📜 ⬆️ ⬇️

Solving the problem with circular references in ObjC blocks

Much has been written about the blocks in ObjC and the correct work with them, including in the Habré. The question of how to work correctly with self in blocks to avoid circular references is regularly asked at interviews. When using frameworks such as ReactiveCocoa, the number of blocks in the code increases dramatically, while increasing the chance of making a mistake and losing objects in memory. About the attempt to finally solve this problem, metaprogramming for c99 with extensions and blocks + hipster macros with @ under the cut.

Consider the problem and how to solve it evolutionally.
self.block = ^{ [self f1]; [self f2]; }; 

This code obviously has a problem. Without the self.block, the object can never be deleted, since the block refers to self. When LANG_WARN_OBJC_IMPLICIT_RETAIN_SELF is enabled, the compiler will even issue a warning.

Improvement 1:

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

The circular reference problem has been resolved, but another one occurs. At the time of the block call, the weakSelf object either exists or no longer exists. If the object does not already exist, weakSelf == nil, m1 and m2 will not be called - it would seem that everything is in order. However, it may happen that at the time of calling m1, the object still exists, but at the time of calling m2, it no longer exists. In this case, m1 will be called, and m2 is not - this behavior may be unexpected and incorrect. This can occur as with race condition in a multi-threaded application, or if m1 reduces the number of references to an object (for example, removes an object from any collection). In the case of CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK and CLANG_WARN_OBJC_RECEIVER_WEAK included, the compiler issues a warning for this case.

Improvement 2:

 __weak typeof(self)weakSelf = self; self.block = ^{ __strong typeof(self)strongSelf = weakSelf; [strongSelf m1]; [strongSelf m2]; }; 

The problem with consistency of method calls inside the block is solved. But a new one is revealed:
 __weak typeof(self)weakSelf = self; self.block = ^{ __strong typeof(self)strongSelf = weakSelf; [strongSelf m1]; [strongSelf m2]; NSAssert(foo == bar, @"Cool assert!") }; 

Macros, such as NSAssert and RACObserve, implicitly use self, and a cyclic reference problem is returned.
')
Improvement 3:

 __weak typeof(self)weakSelf = self; self.block = ^{ __strong typeof(self)self = weakSelf; [self m1]; [self m2]; NSAssert(foo == bar, @"Cool assert!") }; 

Now the problem with macros using self is resolved, but when GCC_WARN_SHADOW is turned on, the compiler issues a warning.

Improvement 4:

The libextobjc library has @weakify and @stongify macros that remove the compiler warning and simplify the code a bit.
 @weakify(self); // self      __weak self.block = ^{ @strongify(self); // self      __strong [self m1]; [self m2]; NSAssert(foo == bar, @"Cool assert!") }; 

This is almost the optimal solution, but it still has several drawbacks: you need to remember to put @weakify and @strongify in the right places; using self after @weakify is safe, but the compiler may issue a warning.
At the same time, there still remains the probability of accidentally capturing in the self block with a strong link:
 @weakify(self); // self      __weak self.block = ^{ @strongify(self); // self      __strong [self m]; NSLog(@"Ivar value form object: %@", _ivar); //    self      _ivar NSAssert(foo == bar, @"Cool assert!") }; 

In order to avoid this, you must either use only access via property (self.ivar), or use the overridden self explicitly:
 @weakify(self); // self      __weak self.block = ^{ @strongify(self); // self      __strong [self m]; NSLog(@"Ivar value form object: %@", self->_ivar); //     self    _ivar NSAssert(foo == bar, @"Cool assert!") }; 

It should be remembered that self can be nil, and the explicit dereference of self -> _ ivar will cause a crash.

Taking into account all these problems, an idea arose to write a macro that will modify not the self, but the block itself in such a way that:

The macro should work approximately like a decoder function in Python, take a block as input, and wrap it in a new block wrapper that is compatible with the parameters and return value. For example, consider the block:
 self.block = ^(NSObject *obj) { NSLog(@"%@ %@", [self description], obj); return 0; }; 

Let's start modifying the block so that self is captured as a weak link, by analogy with the code from "Improvement 1". To do this, we need a new scope in which this local link will be declared. As such a scope, an anonymous block is appropriate, which is called immediately after creation:
 self.block = ^{ __weak typeof(self) weakSelf = self; return ^(NSObject *obj) { NSLog(@"%@ %@", [weakSelf description], obj); return 0; }; }(); 

The compiler will automatically display the return type for the external unnamed block, everything remains type safe.

Now you need to somehow make it so that at the time of the call inside the body of the internal block, self becomes a strong link. For this, it is necessary to divide the block into 2 parts: a declaration of the type ^ (NSObject * obj) and, in fact, the body itself in {...}. Let's transform the body of our block into a block without parameters and place its call into another block created using a type declaration that turns self into a strong link:
 self.block = ^{ __weak typeof(self) weakSelf = self; return ^(NSObject *obj) { __strong typeof(self)self = weakSelf; return ^ (void) { NSLog(@"%@, %@", [self description], obj); return 0; }(); }; }(); 

The main trick is to replace the source block, equivalent to it, but which implicitly captures the weakSelf instead of self, and at the time of the call turns it into a strongSelf.
 return ^(NSObject *obj) { __weak typeof(self)self = weakSelf; return ^ (void) { NSLog(@"%@, %@", [self description], obj); return 0; }(); }; 

essentially the same as
 ^(NSObject *obj) { NSLog(@"%@ %@", [self description], obj); return 0; }; 

Total instead of one block is created three. Since the outermost block is called immediately after creation, you can get rid of it using the code block evaluation evaluation aka statement expressions extension :
 self.block = ({ __weak typeof(self) weakSelf = self; ^(NSObject *obj) { __strong typeof(self)self = weakSelf; return ^ (void) { NSLog(@"%@, %@", [self description], obj); return 0; }(); }; }); 

It remains to wrap the entire boilerplate in a macro, so that this trick was convenient to use. If you leave only the common code, you get:
  ({ __weak typeof(self) weakSelf = self; /*   */ { __strong typeof(self)self = weakSelf; return ^ (void) { /*   */ } (); }; }) 

The first idea was to make a macro with two parameters, for type and body, which would be called like this:
 self.block = weakself(^(NSObject *obj), { NSLog(@"%@ %@", [self description], obj); return 0; }); 

but, unfortunately, when preprocessing, macros are deployed in one line, and, as a result, it is impossible to set a breakpoint on an arbitrary line in the body of the block. Therefore, I had to do this:
 self.block = weakself(^(NSObject *obj)) { NSLog(@"%@ %@", [self description], obj); return 0; } weakselfend ; 

this option is equivalent to @ weakify / @strongify from "Enhancement 4". Macro code:
 #define weakself(ARGS) \ ({ __weak typeof(self) _private_weakSelf = self; \ ARGS { \ __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \ return ^ (void) { #define weakselfend } (); }; }) 

One of the goals when creating a macro was to protect itself from the implicit capture of self when accessing ivar. Unfortunately, I did not think up how to do this in compile time. The only option is assert / log for the debug version when creating a block (it is enough just to create a block so that the test works, it is not necessary to call it). Here it is worth recalling a little about how memory management works for blocks and objects that they capture. There are 3 types of blocks:

Thus, in order to check whether a block captures a strong reference to self, you need to compare the reference count to self, before the block was transferred to the heap, and after. If the counter has increased, then the block captured self by a strong link. This check cannot be reliable in 100% of cases, since the self-reference counter may change from other threads during block transfer to heap, however, this situation is unlikely in a normal program and is quite suitable for a Debug build.

The object could have used the retainCount method to get the reference count, but it is no longer available with ARC, but CFGetRetainCount still works through toll-free bridging. It remains only to insert the calls to this function with the self parameter in the right places and compare the results.
 self.block = {( __weak typeof(self) weakSelf = self; //      self    ^(NSObject *obj) { __strong typeof(self)self = weakSelf; return ^ (void) { NSLog(@"%@, %@", [self description], obj); return 0; }(); }; }) //     .         statement expression 

The problem is that the result of statement expressions is the last line in it. The behavior is similar to an anonymous block, which is called immediately after the declaration. Since the last line of the statement expression is a block declaration, in order for this block to remain valid, the compiler will transfer it to heap. So, we can save the CFGetRetainCount call for self to a local variable inside the statement expression, and we need to make the second CFGetRetainCount call after the last line of the statement expression. If we were talking about C ++, we could create an object on the stack, and in the object's destructor we could do everything we need, since the destroyer would be called after the last line of the statement expression. Fortunately, clang supports gcc-extension which allows you to set a cleanup function (destructor analogue) for any variable on the stack that will be called when the variable goes out of scope. The @onExit macro from libextobjc runs through this extension.

To implement the reference count check, an additional structure is needed:
 struct RefCountCheckerData { CFTypeRef weakSelf; NSUInteger refCountBefore; }; 

And the function that will be exposed as cleanup.
 static inline void vbr_CheckRefCountForWeakSelf(struct RefCountCheckerData *data) { const NSInteger refCountAfter = CFGetRetainCount(data->weakSelf); const NSInteger countOfSelfRefInBlock = refCountAfter - data->refCountBefore; if (countOfSelfRefInBlock > 0) { raise(SIGPIPE); } } 

Create a structure on the stack, set the cleanup function, and initialize a pointer to the weakSelf and the number of references to it. The cleanup function will be called when the _private_refCountCheckerData variable goes out of scope, and at this point our block is already in heap.
 self.block = {( __weak typeof(self) weakSelf = self; __attribute__((cleanup(vbr_CheckRefCountForWeakSelf), unused)) struct RefCountCheckerData _private_refCountCheckerData = { .weakSelf = (__bridge CFTypeRef)self, .refCountBefore = CFGetRetainCount((__bridge CFTypeRef)self), }; ^(NSObject *obj) { __strong typeof(self)self = weakSelf; return ^ (void) { NSLog(@"%@, %@", [self description], obj); return 0; }(); }; }); 

With this version of the macro, the breakpoint in the debugger will work when trying to access ivar not via self, for example, self.block = ^ {NSLog (@ "% d", _ivarInteger); };

Before you submit the final version of the macro, you need to bring it into a modern hipster view. For ObjC, it is fashionable to make macros that begin, like keywords of a language, with @, for example: @strongify, @onExit. But the preprocessor does not allow using @ as part of the macro name. In extobjc, for this purpose, use the insert at the beginning of the macro autoreleasepool {} or try {} catch (...) {}, the @ symbol is thus pasted to either try or to autoreleasepool. After the macro is expanded, an unnecessary empty autoreleasepool or try catch appears in the code, but nobody cares much about it. However, this approach does not work for weakself macro, because the result of weakself is an expression, and the expression cannot contain @autoreleasepool try {} catch (...) {} at the beginning.
 self.block = @weakself(^(NSObject *obj)) { NSLog(@"%@ %@", [self description], obj); return 0; } @weakselfend ; 

When it comes to complex expressions in C, the ternary operator comes to mind first. It remains to understand how to apply it. The first thing that came to mind was to write something like this: self.block = @ 1? / * here is the block code * /: nil;

To do this, just add 1? weakself and: nil; at the end of the weakselfend. But self.block = 1? / * here is the block code * /: nil; quite correct expression, so @weakself and weakself will work.

Option self.block = @ []? / * here is the block code * /: nil; does not allow using @weakself without @, however, after checking the disassembler, it turned out that the optimizer does not throw away the creation of an empty array, but this is an extra overhead at runtime.

Finally, I got the idea to use the features of String Literal Concatenation in ObjC.
 const char *s0 = "ABC" "DEF"; //   C- "ABCDEF" NSString *s1 = @"ABC" @"DEF"; //   ObjC- @"ABCDEF" NSString *s2 = @"ABC" "DEF"; //    ObjC- @"ABCDEF" NSString *s3 = "ABC" @"DEF"; //     

So, the final version of the macro:
 #define weakself(ARGS) \ "weakself should be called as @weakself" @"" ? \ ({ __weak typeof(self) _private_weakSelf = self; \ ARGS { \ __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \ return ^ (void) { #define weakselfnotnil(ARGS) \ "weakself should be called as @weakself" @"" ? \ ({ __weak typeof(self) _private_weakSelf = self; \ ARGS { \ __strong typeof(_private_weakSelf) self __attribute__((unused)) = _private_weakSelf; \ return ^ (void) { if (self) #define weakselfend \ try {} @finally {} } (); }; \ }) : nil 

@weakselfnotnil differs in that if by the time the self block is called, the self is already deleted, the block will not be called. It is suitable only for cases when the block has no return value, otherwise it is not clear what to return in case self has already been deleted. Made mainly for the safe use of ivar through explicit dereference to self:
 self.block = @weakselfnotnil(^) { NSLog(@"%d", self->_ivar); } @weakselfend; 


Performance


It’s probably not worth worrying about performance because there shouldn't be too much overhead. The trick to add @ to the beginning of a macro is completely thrown away by the optimizer. With the overhead of calling an additional block things are more interesting. To check how things are with the overhead costs, we consider 2 cases using macros from libextobjc and our weakself:
 - (void)m1 { @weakify(self); self.block = ^(NSObject * obj) { @strongify(self); NSLog(@"%@", [self description]); return 0; }; } - (void)m2 { self.block = @weakself(^(NSObject * obj)) { NSLog(@"%@", [self description]); return 0; } @weakselfend; } 

We compile with -O3, open it in Hooper and look at the pseudo-code for both cases.
 function -[ViewController m1] { asm{ vst1.64 {d8, d9, d10, d11}, [r4:128]! }; asm{ vst1.64 {d12, d13, d14, d15}, [r4:128] }; r1 = *_NSConcreteStackBlock; *((sp - 0x40 & !0xf) - 0x50) = r1; var_4 = 0xc2000000; var_24 = ((sp - 0x40 & !0xf) - 0x50) + 0x14; asm{ stm.w r5, {r1, r2, r3} }; r5 = [r0 retain]; objc_initWeak(var_24, r5); [r5 release]; r0 = *__objc_personality_v0; r1 = *0xac24; var_52 = r0; var_56 = GCC_except_table0; var_60 = &var_12; var_68 = (sp - 0x40 & !0xf) - 0x50; var_64 = (r1 | 0x1) + 0xabc4; var_32 = 0x1; [r5 setBlock1:(sp - 0x40 & !0xf) - 0x50]; objc_destroyWeak(var_24); r0 = _Unwind_SjLj_Unregister(&var_28); asm{ vld1.64 {d8, d9, d10, d11}, [r4:128]! }; asm{ vld1.64 {d12, d13, d14, d15}, [r4:128] }; Pop(); Pop(); Pop(); return r0; } function ___20-[ViewController m1]_block_invoke { r4 = objc_loadWeakRetained(r0 + 0x14); r0 = [r4 description]; r5 = [r0 retain]; NSLog(@"%@", r5); [r5 release]; [r4 release]; return 0x0; } function -[ViewController m2] { r4 = r0; r0 = *_NSConcreteStackBlock; *(sp - 0x18) = r0; var_4 = 0xc2000000; asm{ stm.w r3, {r0, r1, r2} }; objc_initWeak((sp - 0x18) + 0x14, r4); r5 = objc_retainBlock(sp - 0x18); objc_destroyWeak((sp - 0x18) + 0x14); [r4 setBlock1:r5]; r0 = [r5 release]; return r0; } function ___20-[ViewController m2]_block_invoke { r4 = objc_loadWeakRetained(r0 + 0x14); r0 = [r4 description]; r5 = [r0 retain]; NSLog(@"%@", r5); [r5 release]; [r4 release]; return 0x0; } 


It turns out that weakself is more efficient than @ weakify / strongify, the internal additional block is completely zainlaynitsya and _block_invoke in both cases looks the same. But the way that in extobjc "eat" @ at the beginning of the macro adds a useless code for exception handling in runtime, as seen by _Unwind_SjLj_Unregister.
In the case of compiling with -Os, everything is not so good, the block is not inline and instead of one _block_invoke two are generated.
 function ___20-[ViewController m2]_block_invoke { r0 = objc_loadWeakRetained(r0 + 0x14); r1 = *_NSConcreteStackBlock; *(sp - 0x18) = r1; var_4 = 0xc2000000; asm{ stm.w r4, {r1, r2, r3} }; var_20 = r0; r4 = [r0 retain]; r5 = ___20-[ViewController m2]_block_invoke_2(sp - 0x18); [var_20 release]; [r4 release]; r0 = r5; return r0; } function ___20-[ViewController m2]_block_invoke_2 { r0 = *(r0 + 0x14); r0 = [r0 description]; r4 = [r0 retain]; NSLog(@"%@", r4); [r4 release]; return 0x0; } 


Unfortunately, the clang does not yet allow adding the always_inline attribute to the block.
Full source code and autocomplete for Xcode here .

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


All Articles