Last Updated: May 11, 2020
·
21.59K
· rabovik

Making all self references in blocks weak by default

![Picture][image]

Problem

Every iOS programmer who have to deal with asynchronous block-based code is familiar with the [problem of retain cycles][progWithBlocks] caused by self captured in block:

self.block = ^{
    // Ooops, self is retained by block 
    // which is retained by self.
    // We have a retain cycle here.
    [self doSomeWork];
};

And well-known obvious solution here is using weak references:

__typeof(self) __weak weakSelf = self;
self.block = ^{
    [weakSelf doSomeWork];
};

If you (like me) are tired of seeing dozens of weakSelf variables in your code, may be you share my dream of having all references to self inside any block been weak by default.
If we have a dream, why not to make it come true? At least, just for fun?

Solution

Let me introduce a weakifySelf macros.

self.block = weakifySelf(^{
    // self is weak!
    [self doSomeWork];
});

It takes a block (lets call it a target block) of any signature as an argument and makes all references to self in it weak.
The sources are at [github][gist].

How does this magic work?

Lets build this macros step by step.

  1. To have all references to self been weak we need a scope with a self variable declared as weak. The simplest way to create such scope is to declare another block:

    void(^intermediateBlock)(__typeof(self) __weak self) =
        ^(__typeof(self) __weak self) {
            // self is weak in this scope
            ^{
                // self is weak here too
                [self doSomeWork];
            };
        };

    Here we declare an intermediate block with weak self variable as an argument.

  2. Now we need to pass a weak self reference to our intermediate block. So we write a special function which duty is quite simple: make a weak reference from the strong reference and pass it to the block:

    void rs_blockWithWeakifiedSelf(id self,
        void(^intermediateBlock)(id __weak self))
    {
        id __weak weakSelf = self;
        intermediateBlock(weakSelf);
    }
    // …
    rs_blockWithWeakifiedSelf(self,
        ^(__typeof(self) __weak self) {
            ^{
                [self doSomeWork];
             };
        }
    );
  3. OK, now our target block is declared in a scope where self is weak. But our initial goal was to save the block to the variable. So we:

    1) change intermediate block so that it will return our target block;

    2) in function we will return the result of calling intermediate block, i.e. our target block.

    id rs_blockWithWeakifiedSelf(id self,
        id(^intermediateBlock)(id __weak self))
    {
        id __weak weakSelf = self;
        return intermediateBlock(weakSelf);
    }
    // …
    self.block = rs_blockWithWeakifiedSelf(self,
        ^id(__typeof(self) __weak self) {
            return ^{
                [self doSomeWork];
            };
        }
    );
  4. Now all we have to do is move code to macros:

    #define weakifySelf(BLOCK) \
        rs_blockWithWeakifiedSelf(self, \
        ^id(__typeof(self) __weak self) { \
            return BLOCK; \
        })
    // …
    self.block = weakifySelf(^{
        [self doSomeWork];
    });

Extra bonus

Another common task is creating a strong reference from weak:

self.block = weakifySelf(^{
    __typeof(self) __strong strongSelf = self;
    if (strongSelf) {
        // some potentially unsafe work
        strongSelf->_i = 42;
    }
});

And here are two simple macros simplifying routine:

  • strongify(variable) - creates a strong reference to a variable that will shadow the original

    self.block = weakifySelf(^{
        strongify(self);
        if (!self) return;
        self->_i = 42;
    });
  • strongifyAndReturnIfNil(variable)

    self.block = weakifySelf(^{
        strongifyAndReturnIfNil(self);
        self->_i = 42;
    });

So our final code may look like

-(void)someMethod{
    self.block = weakifySelf(^{
        // Self may be nil here
        [self doSomeWork];
        strongifyAndReturnIfNil(self);
        // Self is strong and not nil.
        // We can do ivars dereferencing
        // and other stuff safely
        self->_i = 42;
    });
}

The final strokes

  • Depends of build settings compiler may produce warnings reminding that we shadow variables. To silence them we need to add some pragma directives. See [sources][gist] for details.

    _Pragma("clang diagnostic ignored \"-Wshadow\"")
  • We still need to be careful and never use instance variables inside blocks directly. For example this will cause a retain cycle:

    self.block = weakifySelf(^{
        // Never do so!
        _i = 42;
    });

    Instead we always must do self dereferencing:

    self.block = weakifySelf(^{
        strongifyAndReturnIfNil(self);
        self->_i = 42;
    });

[gist]: https://gist.github.com/4707815
[progWithBlocks]: http://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW16
[image]: https://coderwall-assets-0.s3.amazonaws.com/uploads/picture/file/1234/56944.png
[statements]:http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs

6 Responses
Add your response

weakifySelf(^{ [NSString stringWithFormat:@"commas make me %@", @"sad"]; });

[!] Too many arguments provided to function-like macro invocation

weakifySelf(^{ ([NSString stringWithFormat:@"paran wrapped commas make me %@", @"happy"]); });

over 1 year ago ·

@hafthor Excellent point, thanks!
Adding ... to macro declaration fixed this issue:

#define weakifySelf(BLOCK...)
over 1 year ago ·

Question: self is the reserve keyword, why it can be argument without problem?

over 1 year ago ·

@doon Every method in Objective-C goes in pair with plain C function implementing it. self and _cmd are just the first parameters of each implementation, not the reserved keywords.

over 1 year ago ·

Thank you, rabovik. I went through the runtime guide, then I understood.

over 1 year ago ·

this is really nice macro but somehow after switching to using it, xcode won't let me debug with breakpoint within the block anymore

over 1 year ago ·