📜 ⬆️ ⬇️

About blocks and their use in Objective-C part 3

Just like in the topic - “About blocks and their use in Objective-C part 2” , we will continue to talk about the advantages of using blocks with live examples.
Here we look at the usability of blocks when managing a sequence of operations.

5. UIView animation, sequence of animations.

To begin with, we will write a simple example in which we will move a button using animations (without blocks). Then we change the order of the animations to see what changes will be needed in the code.

Step 1

Create four animations: "move the button up", "... down", "... to the right" and "... left". Accordingly, in the methods: moveUpAnimation, moveDownAnimation, moveRightAnimation and moveLeftAnimation.
Here is an example of one of the animations:
static const CGFloat button_offset_ = 20.f;

-( void )moveUpAnimation
{
[ UIView beginAnimations: nil context: nil ];

CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );

[ UIView commitAnimations ];
}


* This source code was highlighted with Source Code Highlighter .

Then write the code in such a way that these four animations move the button along the contour of the screen in a clockwise direction. Performing these animations is simply sequential:
-(IBAction)animateButtonAction:( id )sender_
{
[ self moveUpAnimation ];
[ self moveRightAnimation ];
[ self moveDownAnimation ];
[ self moveLeftAnimation ];
}


* This source code was highlighted with Source Code Highlighter .

will give nothing. We will only see the latest animation. The correct solution is to run the next animation at the end of the previous one. To implement our plans, we need to set the animation delegate and, in the context of the animation, transfer information about the next animation. In the delegate, perform the following animation from the context. For example:
// ()
@ interface JFFNextAnimation : NSObject

@property ( nonatomic, retain ) UIViewAnimationsExampleViewController* controller;
@property ( nonatomic, assign ) SEL nextAnimationSelector;

@end

-( void )moveUpAnimation
{
JFFNextAnimation* next_animation_ = [ JFFNextAnimation new ];
//
next_animation_.controller = self;
next_animation_.nextAnimationSelector = @selector( moveRightAnimation );
//
[ UIView beginAnimations: nil context: next_animation_ ];

CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );

//
[ UIView setAnimationDelegate: self ];

[ UIView commitAnimations ];
}

// moveDownAnimation, moveRightAnimation moveLeftAnimation
-( void )animationDidStop:( NSString* )animation_id_
finished:( NSNumber* )finished_
context:( void * )context_
{
//
JFFNextAnimation* context_object_ = context_;
[ context_object_.controller performSelector: context_object_.nextAnimationSelector ];
[ context_object_ release ];
}

-(IBAction)animateButtonAction:( id )sender_
{
//
[ self moveUpAnimation ];
}

* This source code was highlighted with Source Code Highlighter .

Now everything works correctly. But let's say we want an animation of moving the button not clockwise, but counterclockwise. Then we will need to change the code of each of the methods moveUpAnimation, moveDownAnimation, moveRightAnimation and moveLeftAnimation. This is not very convenient, so we will rewrite our code so that this task is easier to solve.

Step 2

Change the sequence of animation calls. To begin with, let's keep in context not the selector of the next animation, but all the animations that need to be performed after the current one:
@ interface JFFNextAnimation : NSObject

@property ( nonatomic, retain ) UIViewAnimationsExampleViewController* controller;
// ,
@property ( nonatomic, retain ) NSMutableArray* nextAnimations;

@end


* This source code was highlighted with Source Code Highlighter .

The code for the moveUpAnimation, moveDownAnimation, moveRightAnimation, and moveLeftAnimation methods also need to be changed:
// ,
//
-( void )moveUpAnimationWithNextAnimation:( JFFNextAnimation* )next_animation_
{
[ UIView beginAnimations: nil context: next_animation_ ];

CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );

[ UIView setAnimationDelegate: self ];

[ UIView commitAnimations ];
}

// moveDownAnimation, moveRightAnimation moveLeftAnimation


* This source code was highlighted with Source Code Highlighter .

The animation delegate also needs to be redone:
-( void )animationDidStop:( NSString* )animation_id_
finished:( NSNumber* )finished_
context:( void * )context_
{
// -
if ( !context_ )
return ;

JFFNextAnimation* context_object_ = context_;

//
NSString* next_animation_string_ = [ context_object_.nextAnimations objectAtIndex: 0 ];
next_animation_string_ = [ [ next_animation_string_ retain ] autorelease ];
//
[ context_object_.nextAnimations removeObjectAtIndex: 0 ];

SEL next_animation_sel_ = NSSelectorFromString( next_animation_string_ );

if ( [ context_object_.nextAnimations count ] == 0 )
{
//
//
[ context_object_.controller performSelector: next_animation_sel_
withObject: nil ];
//
[ context_object_ release ];
}
else
{
//
[ context_object_.controller performSelector: next_animation_sel_
withObject: context_object_ ];
}
}

* This source code was highlighted with Source Code Highlighter .

And of course, the result for which we worked, now the sequence of animations is easy to change:
-(IBAction)animateButtonAction:( id )sender_
{
JFFNextAnimation* next_animation_ = [ JFFNextAnimation new ];
next_animation_.controller = self;
//
next_animation_.nextAnimations = [ NSMutableArray arrayWithObjects:
@"moveUpAnimationWithNextAnimation:"
, @"moveLeftAnimationWithNextAnimation:"
, @"moveDownAnimationWithNextAnimation:"
, nil ];

//
[ self moveRightAnimationWithNextAnimation: next_animation_ ];
}


* This source code was highlighted with Source Code Highlighter .

All code for the results can be found at gihub .
')
Results:

The task at the beginning of the topic, we certainly decided. But the cost of the solution is high, the code is insecure (strings instead of selectors), complicated (the confusing logic of the delegate of the selector and the management of the context memory), is error-prone. We will try to partially correct the situation using (of course, according to the name of the topic) blocks. So…

Step 3

We rewrite animations using block api. The first thing we can do is remove the context animation class - JFFNextAnimation and the delegate animation method; they won't be useful to us anymore. The moveUpAnimation method is simplified to the following form:
-(JFFSimpleBlock)moveUpAnimationBlock
{
return [ [ ^
{
CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );
} copy ] autorelease ];
}


* This source code was highlighted with Source Code Highlighter .

Add an auxiliary method creating a block that performs the animation:
//
//
-(JFFSimpleBlock)animationBlockWithAnimations:( JFFSimpleBlock )animations_
completion:( JFFSimpleBlock )completion_
{
// ,
//
completion_ = [ [ completion_ copy ] autorelease ];
return [ [ ^
{
[ UIView animateWithDuration: 0.2
animations: animations_
completion: ^( BOOL finished_ )
{
if ( completion_ )
completion_();
} ];
} copy ] autorelease ];
}


* This source code was highlighted with Source Code Highlighter .

And we define the sequence of the animations themselves:
-(IBAction)animateButtonAction:( id )sender_
{
// , ,
JFFSimpleBlock move_left_animation_block_ = [ self moveLeftAnimationBlock ];
//completion: - ,
move_left_animation_block_ = [ self animationBlockWithAnimations: move_left_animation_block_
completion: nil ];

JFFSimpleBlock move_down_animation_block_ = [ self moveDownAnimationBlock ];
//completion: - - "move left"
move_down_animation_block_ = [ self animationBlockWithAnimations: move_down_animation_block_
completion: move_left_animation_block_ ];

JFFSimpleBlock move_right_animation_block_ = [ self moveRightAnimationBlock ];
//completion: - - "move down"
move_right_animation_block_ = [ self animationBlockWithAnimations: move_right_animation_block_
completion: move_down_animation_block_ ];

//
JFFSimpleBlock move_up_animation_block_ = [ self moveUpAnimationBlock ];
//completion: - - "move right"
move_up_animation_block_ = [ self animationBlockWithAnimations: [ self moveUpAnimationBlock ]
completion: move_right_animation_block_ ];

//
move_up_animation_block_();
}


* This source code was highlighted with Source Code Highlighter .

Now, as in the previous example (animations without blocks), we will try to change the sequence of animation calls. Fortunately, this is not as difficult as it was in our very first example, but we will not stop there.

Step 4

Let's go a little ahead and look at the sequenceOfAsyncOperations function. This function takes several blocks that abstract asynchronous operations, in the form of arguments, and returns a new block, which, when called, will execute the block arguments of this function in the specified order. The block of an asynchronous operation itself is of type JFFAsyncOperation , therefore we slightly change the animationBlockWithAnimations: function according to this type:
-(JFFAsyncOperation)animationBlockWithAnimations:( JFFSimpleBlock )animations_
{
return [ [ ^( JFFAsyncOperationProgressHandler progress_callback_
, JFFCancelHandler cancel_callback_
, JFFDidFinishAsyncOperationHandler done_callback_ )
{
done_callback_ = [ [ done_callback_ copy ] autorelease ];
[ UIView animateWithDuration: 0.2
animations: animations_
completion: ^( BOOL finished_ )
{
if ( done_callback_ )
done_callback_( [ NSNull null ], nil );
} ];
//
JFFCancelAsyncOpration cancel_block_ = ^{ /*do nothing*/ };
return [ [ cancel_block_ copy ] autorelease ];
} copy ] autorelease ];
}


* This source code was highlighted with Source Code Highlighter .

And we get the result:
-(IBAction)animateButtonAction:( id )sender_
{
JFFSimpleBlock move_right_animation_block_ = [ self moveRightAnimationBlock ];
JFFAsyncOperation move_right_async_block_ = [ self animationBlockWithAnimations: move_right_animation_block_ ];

JFFSimpleBlock move_up_animation_block_ = [ self moveUpAnimationBlock ];
JFFAsyncOperation move_up_async_block_ = [ self animationBlockWithAnimations: move_up_animation_block_ ];

JFFSimpleBlock move_left_animation_block_ = [ self moveLeftAnimationBlock ];
JFFAsyncOperation move_left_async_block_ = [ self animationBlockWithAnimations: move_left_animation_block_ ];

JFFSimpleBlock move_down_animation_block_ = [ self moveDownAnimationBlock ];
JFFAsyncOperation move_down_async_block_ = [ self animationBlockWithAnimations: move_down_animation_block_ ];

//
// - sequenceOfAsyncOperations
JFFAsyncOperation result_animation_block_ = sequenceOfAsyncOperations(
move_right_async_block_
, move_up_async_block_
, move_left_async_block_
, move_down_async_block_
, nil );

// ,
result_animation_block_( nil, nil, nil );
}


* This source code was highlighted with Source Code Highlighter .

On gihub you can see all the received code.

That's all for now. Thanks for attention. If this topic is interesting, then in the next article I will try to talk about managing asynchronous operations using blocks.

Source: https://habr.com/ru/post/121958/


All Articles