About Blocks and Their Use in Objective-C Part 1

    In OS X 10.6 and iOS 4.0, Apple announced support for blocks , which are essentially closures . Further about the blocks in the context of development for iOS, Objective-C (i.e., work without gc ).
    To use iOS ver. <4.0, you can apply ESBlockRuntime or PLBlocks .

    Briefly about the theory


    The block instance, block type, and block literal itself are indicated using the ^ operator, for example:

    typedef int (^MyBlock)(int);

    int multiplier = 7;
    MyBlock myBlock = ^(int num) {
     return num * multiplier;
    };

    * This source code was highlighted with Source Code Highlighter.

    or

    int multiplier = 7;
    int (^myBlock)(int) = ^(int num) {
      return num * multiplier;
    };

    * This source code was highlighted with Source Code Highlighter.

    A block call is similar to a call to a regular function. For example, like this:

    myBlock( 3 )

    * This source code was highlighted with Source Code Highlighter.

    The main feature of the blocks is their ability to store the context in which they were created. In the example above, “myBlock” will always multiply the number by 7. How does this all work?

    Types of block context variables

    1. Primitive types C and structures, blocks are stored as constants. Example:


    int multiplier = 7;
    int (^myBlock)(int) = ^(int num) {
      return num * multiplier;
    };
    multiplier = 8;
    NSLog( @"%d", myBlock( 3 ) );

    * This source code was highlighted with Source Code Highlighter.

    Prints - 21, not 24.

    2. The variables specified with the __block keyword are mutable. This works by copying the value of such a variable into a heap and each block stores a link to this variable. Example:

    __block int multiplier = 7;
    int (^myBlock)(int) = ^(int num) {
      return num * multiplier;
    };
    multiplier = 8;
    NSLog( @"%d", myBlock( 3 ) );

    * This source code was highlighted with Source Code Highlighter.

    Prints - 24, not 21.

    3. Variables - pointers to objects with reference counting (id, NSObject). Retain is called for them when the block is copied to the heap. Example:

    NSDate* date = [ [ NSDate alloc ] init ];

    void (^printDate)() = ^() {
     NSLog( @"date: %@", date );
    };

    //копируем блок в кучу
    printDate = [ [ printDate copy ] autorelease ];

    [ date release ];

    printDate();

    * This source code was highlighted with Source Code Highlighter.

    Here I want to draw your attention to the fact that retain of the date object occurs exactly when the block is copied to the heap, and not during its creation. For example, this code will fall from “EXC_BAD_ACCESS”

    NSDate* date = [ [ NSDate alloc ] init ];

    void (^printDate)() = ^() {
     NSLog( @"date: %@", date );
    };

    [ date release ];

    //копируем блок в кучу и падаем
    printDate = [ [ printDate copy ] autorelease ];

    printDate();

    * This source code was highlighted with Source Code Highlighter.

    4. Variables - pointers to objects with reference counting (id, NSObject) declared with the __block keyword. Retain is NOT called for them when copying a block to the heap. Example:
    __block NSDate* date = [ [ NSDate alloc ] init ];

    void (^printDate)() = ^() {
      //здесь падаем при обращении к date
      NSLog( @"date: %@", date );
    };

    //копируем блок в кучу, для объекта date retain не вызывается
    printDate = [ [ printDate copy ] autorelease ];

    [ date release ];

    printDate();

    * This source code was highlighted with Source Code Highlighter.

    This is usually used to avoid circular references. Example:
    @interface SomeClass : NSObject

    //копируем блок проперти
    @property ( nonatomic, copy ) SimpleBlock block;

    @end

    @implementation SomeClass

    @synthesize block = _block;

    -(void)dealloc
    {
      [ _block release ];

      [ super dealloc ];
    }

    -(void)methodB
    {
    }

    -(void)methodA
    {
      __block SomeClass* self_ = self;
      //потенциально циклическая ссылка( утечка ) - класс хранит блок, а блок ссылается на класс
      self.block = ^()
      {
       //здесь retain для self_ не вызывается
       [ self_ methodB ];
      };
    }

    @end

    * This source code was highlighted with Source Code Highlighter.

    Blocks are instances of the NSObject class (specific classes of these objects are not defined), so we can and are forced to use the methods of the NSObject class - copy, retain, release and autorelease for blocks. But why do we need this?

    Blocks and memory management

    By default, block instances are not created on the heap, as one might assume, but on the stack. Therefore, if necessary, make a deferred call to the block, first you need to copy it to the heap.

    Suppose there is an extension of the NSObject class with the "performAfterDelay:" method, which executes the given block with a delay.

    @implementation NSObject (BlocksExtensions)

    -(void)callSelfBlock
    {
      void* self_ = self;
      ESSimpleBlock block_ = (ESSimpleBlock)self_;
      block_();
    }

    -(void)performAfterDelay:( NSTimeInterval )delay_
    {
      [ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
    }

    @end

    * This source code was highlighted with Source Code Highlighter.

    And, in fact, the call:
      NSDate* date = [ NSDate date ];

      void (^printDate)() = ^() {
       NSLog( @"date: %@", date );
      };

      [ printDate performAfterDelay: 0.3 ];

    * This source code was highlighted with Source Code Highlighter.

    Such code will “dump” our application, because the stack block will be destroyed by the time of the call, and we will turn to random memory in the place where the block was called. Although this code is:
      void (^printDate)() = ^() {
       NSLog( @"date: %@", [ NSDate date ] );
      };

      [ printDate performAfterDelay: 0.3 ];

    * This source code was highlighted with Source Code Highlighter.

    will work great. What is the reason? Please note - the last block does not refer to external variables, therefore there is no need to create a copy of it. In this case, the compiler creates the so-called Global block. There is only one instance of such a block in the program, the lifetime of which is limited by the lifetime of the application. Thus, GlobalBlock can be considered as a singletone object.

    Types of Block Variables

    And so, to summarize. There are three types of blocks: global (stateless), local or stacked, and blocks on the heap (MallocBlock). Therefore, the copy, retain, release and autorelease methods of the global block do nothing. The retain method also does nothing for the stack block. For the Malloc block, the copy method in turn works as retain for NSObject.

    And of course, the revised version of the previous example with the addition of the copy method:
    @implementation NSObject (BlocksExtensions)

    -(void)callSelfBlock
    {
     void* self_ = self;
     ESSimpleBlock block_ = (ESSimpleBlock)self_;
     block_();
    }

    -(void)performAfterDelay:( NSTimeInterval )delay_
    {
      //копируем блок в кучу, так как отложеный вызов - afterDelay:
      self = [ [ self copy ] autorelease ];
      [ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
    }

    @end

    * This source code was highlighted with Source Code Highlighter.

    Continuation: “On Blocks and Their Use in Objective-C Part 2”

    Also popular now: