Table Of Contents

Blocks

In this section, we will discuss the leak of retain cycle in Block programming and how to use iGC to detect and reclaim them, and a case study of Notification block retain cycles, and some other iGC block features.

Block Retain Cycles

What’s the problem?

When a stack block is copied to heap, the Objective-C runtime will call a helper function (emitted by the compiler) to retain objects referenced by the block code, therefore an implicit retain cycle may be formed among blocks and other objects.

A block code can reference four kinds of things:

  • C++ stack based objects
  • References to Objective-C objects
  • Blocks
  • __block variables

Here is an example of Block Retain Cycles in ARC. For demonstration, the code is written as simple as possible.

Listing 1    A leaking example of Block Retain Cycles in ARC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#import <Foundation/Foundation.h>

@interface Foo : NSObject
@property (nonatomic, copy) id blk;
@end

@implementation Foo
@synthesize blk;

- (void)dealloc {
    NSLog(@"dealloc %@", self);
}
@end

static void DoSomeWork(id p) { (void)p;}

static void RunTest() {
    Foo * a = [[Foo alloc] init];
    id b = ^{
        DoSomeWork(a);
    };
    a.blk = b;
}

int main() {
    RunTest();
    NSLog(@"== End of use ==");
    return 0;
}

Build and run. You can see the [dealloc] method was not called. The object and block leak because they form a retain cycle as: a.blk references block; and because block code uses a, the compiler emits a helper function that implicitly retains a.

Let’s see how iGC reclaims these Block retain cycles.

Recompile source code to reclaim cycles

Recompile the above sources with these flags:

  • for ARCGC :

    replace -fobjc-arc with -fobjc-arc-gc

  • for ARCGC_C :

    replace -fobjc-arc with -fobjc-arc-gc -fobjc-arc-gc-compatible,

    or

    append -fobjc-gc to the existing -fobjc-arc

After rebuild and run, you would see the cycle was reclaimed successfully as expected. Similarly, block retain cycles in MRR can be reclaimed via the same way.

Collect all types of block cycles

The above sample shows the case that a block uses an object (Foo) and forms a cycle.

Cycles may also be formed implicitly by other types of block usages, including:

  • references another block,
  • define a __block variable of object
  • define a __block variable of block.

iGC can collects all kinds of Block retain cycles. Here are some samples that no longer leak under iGC.

void ReferenceOtherBlock() {
    Foo * a = [[Foo alloc] init];
    id b = [^{ DoSomeWork(a); } copy];
    a.blk = ^{ DoSomeWork(b); };
}

void ByrefObject() {
    __block Foo * a = [[Foo alloc] init];
    a.blk = ^{ DoSomeWork(a); };
}

void ByrefBlock() {
    Foo * a = [[Foo alloc] init];
    __block id b = [^{ DoSomeWork(a); } copy];
    a.blk = ^{ DoSomeWork(b); };
}

Other block features

  • The iGC feature Automatic Block Copy makes the block programming a little less pain of dangling pointers, as instance variables are forced to point to blocks in heap.

  • iGC makes Block available on 3.x platforms

    Apple’s Block is only available for iOS 4.0 or later. However, under the help of iGC runtime, block code can run on iOS 3.x devices and simulators. For example, you can build an app under Xcode 4.2 for Snow Loepard with iGC sdk, then deploy it to iPhoneOS 3.x (3.0 ~ 3.1.3) devices or simulators (See more: Run on 3.x simulators).

Notification Block Retain Cycles

It is not uncommon for people making mistakes in iOS Notification programming with Block. A naive writing of code may lead to weird behaviours, leaks or even crashes. We will see how iGC 2.0+ is designed to solve this issue.

First, let’s take a look at the following leaking code.

Listing 2    A example of notification cycle leaks in ARC (no leak with iGC)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import <Foundation/Foundation.h>

@interface Foo : NSObject
{
    id observer;
    int count;      // counting # of notifications
}
@end

@implementation Foo
- (id)init {
    if ((self = [super init])) {
        count = 0;
        observer = [[NSNotificationCenter defaultCenter]
                    addObserverForName:NSCurrentLocaleDidChangeNotification
                    object:nil queue:[NSOperationQueue mainQueue]
                    usingBlock:^ (NSNotification *note) {
                        count++;
                    }];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"dealloc %@", self);
    [[NSNotificationCenter defaultCenter] removeObserver:observer];
}
@end

int main() {
    Foo * foo = [[Foo alloc] init];

    foo = nil;
    NSLog(@"== End of use ==");

    return 0;
}
2013-05-19 03:51:52.643 iTestARC[9224:11303] == End of use ==

Build and run, object foo was not deallocated. If you comment out line#18, then foo is deallocated like this:

2013-05-19 03:53:51.126 iTestARC[9300:11303] dealloc <Foo: 0x7148d70>
2013-05-19 03:53:51.129 iTestARC[9300:11303] == End of use ==

The reason is that, the block ^{...} (line#17~#19) accesses ivar count (line#6) of the object foo. As Clang Objective-C compiler follows block specification and emits code to retain object foo at block copying, a retain cycle is formed implicitly as: NSNotificationCenter retains observer object until foo‘s [dealloc] explictly invokes [removeObserver:]; the observer retains block; and block retains foo.

The traditional solution

To avoid the notification retain cycle, a practice pattern is often recommended to break cycle like this:

  1. create a weak reference weak_self to the self (the object that will removes observer);
  2. in block code, use the weak_self to access ivars or send messages, avoid using self directly or implicitly.

Below, we rewrote the above sample using the “weak_self” approach for ARC and MRR.

- (id)init {
    if ((self = [super init])) {
#if __has_feature(objc_arc)                 /*== Automatic Reference Counting ==*/
        // Foo * weak_self;                     // leak!
        __unsafe_unretained Foo *weak_self;     // ok
        // __weak Foo * weak_self;              // leak into autorelease pool(*)

        // __block Foo * weak_self;                     // leak!
        // __block __unsafe_unretained Foo *weak_self;  // also ok
        // __block __weak Foo * weak_self;              // also ok

#else                                       /*== Manual Retain Release ==*/
        __block Foo * weak_self;                // ok
        // Foo * weak_self;                     // leak!
#endif
        weak_self = self;
        count = 0;
        observer = [[NSNotificationCenter defaultCenter]
                    addObserverForName:NSCurrentLocaleDidChangeNotification
                    object:nil queue:[NSOperationQueue mainQueue]
                    usingBlock:^ (NSNotification *note) {
                        weak_self->count++;
                        // [super respondsToSelector:@selector(someMethod)]; <-- leak!
                        // printf("called by %@", observer); <-- leak!
                        // [self someMethod];  <-- leak!
                    }];
    }
    return self;
}

Discussion: using ARC (zeroing) __weak as weak_self

Somebody insists that it should use ARC (zeroing) __weak variable for weak_self, and follow ARC rules in the block code to check-before-use the weak_self variable.

However, it is unnecessary to use ARC __weak for this “Notification Retain Cycle” case, because Apple already demands call removeObserver before related object(s) is deallocated, see [NSNotificationCenter removeObserver:]. That means, before foo is deallocated and weak_self variable is nullified, the block has been removed. Thus, block would not be invoked with a nullified weak_self.

Also, besides ARC __weak cannot be used for pre-iOS5.0 deployment, it may make self leak into autorelease pool. [1]

As you can see, this weak_self approach is so inconvenient and error-prone:

  • leak if block code accidently uses ivar implicitly or self explicitly
  • leak if block code sends a message to super, which cannot be replaced by weak_self
  • inconsistence, __block Foo * weak_self works in MRR but leaks in ARC.
  • __weak Foo * weak_self leaks into autorelease pool

Note

Leaks occur even the block code has never been executed (as in the above sample).

Footnotes

[1]

The access of ARC weak variable puts objects into autorelese pool

When accessing a weak variable in ARC, the compiler emits calls to objc_loadWeak, etc helper functions, which may put the return object in autorelease pool, such as in debug mode and sometimes in release mode.

In the above sample, when the NSNotificationCenter copies the block to heap, weak variable weak_self implicitly put foo into autorelease pool. You have to add @autoreleasepool {...} somewhere to drain the foo out of pool.

The iGC solution

iGC can detect and collect these cycles to make programming easier and stable:

  • you no longer need the weak_self variable, and in Block code you can
  • directly use ivar and send messages to self, and
  • send messages to super.

recompile the sources

You can recompile the leaking Listing 2 source under ARCGC or ARCGC_C, then link it against iGC library. For a better demonstration, add an explicit GC call after line#33 as following.

#include "hnxgc.h"
...
int main() {
    foo = [[Foo alloc] init];
    ...
    foo = nil;
    HXSystem_gc();     // <-- added for better result viewing
    NSLog(@"== End of use ==");
    ..

Note

You need not invoke GC explicitly in a real app, iGC will automatically invoke GC when necessary.

Now, you may see outputs like this:

2013-05-19 02:29:58.107 iTestARCGC[8215:11303] HnxGC garbage collector v3.5-790 (release)
    for ObjC started, Copyright 2006-2012 (c) Harnix Technologies Inc., All Rights Reserved.
2013-05-19 02:29:58.121 iTestARCGC[8215:12c03] dealloc <Foo: 0x7b27910>
2013-05-19 02:29:58.122 iTestARCGC[8215:11303] == End of use ==
... 01:29:58.123 [11303] *** HnxGC garbage collector is shut down. ***

For more outputs add a call to HXSystem_setTraceType:

2013-05-19 02:42:26.559 iTestARCGC[8463:11303] HnxGC garbage collector v3.5-790 (release)
    for ObjC started, Copyright 2006-2012 (c) Harnix Technologies Inc., All Rights Reserved.
... 01:42:26.573 [11303] Create object 0x7bf9910(Foo)
... 01:42:26.575 [11303] Create object 0x9bb7f80(NSNotificationCenter)
... 01:42:26.576 [11303] Create object 0x7bddfa8(NSFileManager)
... 01:42:26.577 [11303] Create object 0x7bf98f0(NSOperationQueue)
... 01:42:26.578 [11303] Create object 0x9bbaa08(__NSOperationQueueInternal)
... 01:42:26.579 [11303] Create object 0x9bbd2c0(__NSObserver)
... 01:42:26.579 [11303] Create object 0x9bbd290(__Block_layout)
... 01:42:26.579 [11303] Create object 0x9bbd260(__Block_layout)
... 01:42:26.579 [11303] Create object 0x9bbd230(__Block_weakByref)
... 01:42:26.579 [12d03] Destructing 0x7bf9910(Foo)
... 01:42:26.580 [12d03] Create object 0x7bf98d0(__NSDate)
... 01:42:26.580 [12d03] Destructing 0x7bf98d0(__NSDate)
2013-05-19 02:42:26.579 iTestARCGC[8463:12d03] dealloc <Foo: 0x7bf9910>
... 01:42:26.581 [12d03] Create object 0x7bfcc70(__Block_layout)
... 01:42:26.581 [12d03] Destructing 0x9bbd2c0(__NSObserver)
... 01:42:26.581 [12d03] Destructing 0x9bbd290(__Block_layout)
... 01:42:26.581 [12d03] Free object 0x7bf98d0(__NSDate)
... 01:42:26.581 [12d03] Free object 0x7bf9910(Foo)
... 01:42:26.581 [12d03] Free object 0x9bbd2c0(__NSObserver)
... 01:42:26.581 [12d03] Free object 0x9bbd290(__Block_layout)
... 01:42:26.581 [12903] Destructing 0x9bbd260(__Block_layout)
... 01:42:26.581 [12903] Destructing 0x9bbd230(__Block_weakByref)
... 01:42:26.581 [12903] Destructing 0x7bfcc70(__Block_layout)
... 01:42:26.582 [11303] Create object 0x7bf9910(__NSDate)
... 01:42:26.582 [11303] Destructing 0x7bf9910(__NSDate)
2013-05-19 02:42:26.582 iTestARCGC[8463:11303] == End of use ==
... 01:42:26.583 [11303] *** HnxGC garbage collector is shut down. ***

iGC ensure removal of obsever first

The Apple’s notification mechanism requires observers be removed from NotificationCenter first.

When iGC detects notification cycles, it will apply reclamation ordering mechanism to ensure observer be unlinked from NotificationCenter before it was deallocated.

You can see in the above outputs, cyclic objects are destruct in this order:

  1. 0x7bf9910(Foo),
  2. 0x9bbd2c0(__NSObserver),
  3. 0x9bbd290(__Block_layout).

That is, -[Foo dealloc] is first executed, thus the call to -[NSNotificationCenter removeObserver:] is prior to others.

Automatic Block Copy

In iGC, whenever a stack block is assigned to an ivar or a property, the system will automatically copy it to heap before taking any actions. This “Automatic Block Copy” feature is enabled by default.

You can disable it through HXSystem_configure with kHXFeature_AutoBlockCopy.

Why do that?

This feature is designed mainly to prevent dangling pointers of an instance variable pointing to a stack block, because instance varible (in heap) may survive longer than a stack block.

Let’s look at the following code:

@interface Foo : NSObject
@property (retain, nonatomic) id aObj;
@end

When assigning a stack block to property aObj, Objective-C runtime will send a retain message to the stack block. However, both [retain] and [release] messages are ignored by stack block (__NSStackBlock) by design. When the execution leave the scope and the block is destroyed, the object of Foo` may be still alive or pass the invalid block to others.

Under “Automatic Block Copy”, stack block is copied to heap before assigning to an ivar. Therefore, no instance variable can point to a stack block (except weak reference, which is ignored by the system).

Discussion

  1. Somebody may say, the aObj should be defined as property(copy) because it represents a block.

    Yes, but

    1. the above code has been compiled successfully without any warning, it is easy for programmers to make a similar mistake.
    2. the designer of this interface may not be aware that the aObj was assigned a stack block, which is an id type but cannot be retained.
  2. Can we change the -[__NSStackBlock retain] implementation to do copying?

    No, because -[copy] method returns a new object, while -[retain] method returns existing self. Many people usually just ignore the return value of -[retain] method, and will never [release] it.

  3. Objective-C as a dynamic language, it is not always possible for compiler to distinguish a stack block from id type. Therefore, some jobs have to be done at runtime, not at compile-time.

Performance

For performance sensitive code, it can use __weak instance variables to avoid block copying.

@property (nonatomic) MyBlock * aStrongBlockReference;

@property (nonatomic) __weak MyBlock * aWeakBlockReference;