I was writing unit tests for an iOS project recently when I stumbled upon a very weird issue with a retain cycle warning from Clang. I created a minimal sample file to demonstrate it:
//// main.m// RetainCycleWarning//// Created by u on 2015-04-04.// Copyright (c) 2015 yes. All rights reserved.//#import <Foundation/Foundation.h>@importXCTest;@interfaceFoo : NSObjecttypedefvoid(^SuccessBlock)(CGFloatvalue);-(void)setValue:(CGFloat)valuesuccess:(SuccessBlock)success;-(void)getValueWithSuccessBlock:(SuccessBlock)success;@end@implementationFoo-(void)setValue:(CGFloat)valuesuccess:(SuccessBlock)success{}-(void)getValueWithSuccessBlock:(SuccessBlock)success{}@end@interfacemainTests : XCTestCase// used in multiple tests@property(nonatomic,strong)Foo*foo;@end@implementationmainTests-(void)setUp{[supersetUp];self.foo=[Foonew];}-(void)tearDown{self.foo=nil;[supertearDown];}-(void)testFoo{[self.foogetValueWithSuccessBlock:^(CGFloatvalue){XCTFail(@"success");}];[self.foosetValue:1.0success:^(CGFloatvalue){XCTFail(@"success");}];}@endintmain(intargc,constchar*argv[]){@autoreleasepool{// insert code here...NSLog(@"Hello, World!");}return0;}
It’s the only file in a OS X command-line tool project. It has the Foo class with two simple methods to get and set a value, with empty implementations. Then, in a test class I create a property for a Foo object (because it’s originally used in multiple test methods), and test that the success blocks are not called. You don’t need to run the tests, because the point is this warning while building:
1234567891011
CompileC Library/Developer/Xcode/DerivedData/RetainCycleWarning-x/Build/Intermediates/RetainCycleWarning.build/Debug/RetainCycleWarning.build/Objects-normal/x86_64/main.o RetainCycleWarning/main.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
// …
RetainCycleWarning/RetainCycleWarning/main.m:62:20: warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles]
XCTFail(@"success");
^~~~~~~~~~~~~~~~~~~
// …
RetainCycleWarning/RetainCycleWarning/main.m:60:6: note: block will be retained by an object strongly retained by the captured object
[self.foo setValue:1.0
^~~~
1 warning generated.
Only one warning even though the get and set tests are the same — just calling XCTFail() in a success block.
__weaktypeof(self)wself=self;^(){typeof(self)sself=wself;// do something with sself}
However, it’s not possible with XCTest macros, which hardcode self in them. A workaround could be noticing that XCTFail() calls _XCTPrimitiveFail(self, …) and use that instead, but that doesn’t look nice. And anyway, why is there only one warning, with setValue:success:, even though both implementations clearly can’t create a retain cycle?!
Experimenting with the code I found that if I change the name from setValue:success: to updateValue:success: removes the warning! It must be something in the name! The only answer I could find to this puzzle is here: http://stackoverflow.com/questions/15535899/blocks-retain-cycle-from-naming-convention. Basically, the method looks like a setter for the compiler, and it thinks the block may be captured. Fair enough, except it’s not the case here.
One way to fix this is to rename the method. What if you can’t do that? Here is my solution: