I am surprised to find the following behavior...
@interface Foo : NSObject
- (void)addBar:(id)aBar withCompletion:(void(^)(void))completion;
@end
@interface AwesomeClass : NSObject
@property (strong, nonatomic) Foo *foo;
- (void)doSomethingWithBar:(id)bar;
@end
@implementation AwesomeClass
- (void)doSomethingWithBar:(id)bar
{
[self.foo addBar:bar withCompletion:^{
NSLog(@"%@", self.foo);
}];
}
In Xcode 4.6.1 I get a warning in the implementation of -doSomethingWithBar:
that "Capturing 'self' strongly in this block is likely to lead to a retain cycle."
However, if I refactor the name of the method -addBar:withCompletion:
to -setupBar:withCompletion:
this warning goes away. It seems that my surprise by this illustrates I've got a gap in my knowledge regarding Objective-C naming conventions!
The code
[self.foo someMethod:bar withCompletion:^{
NSLog(@"%@", self.foo);
}];
does not generally create a retain cycle. If someMethod:withCompletion:
just calls the block and returns, there is no retain cycle at all. (-[NSArray enumerateObjectsUsingBlock:]
is an example.)
Only if someMethod:withCompletion:
"remembers" the block to be executed later, there is a possible retain cycle. So clang uses a heuristic to decide if it is a "setter-like" method that stores the block into a property of Foo
to be executed later.
-set<Key>
and -add<Key>
are accessor patterns in Key-Value Coding to set a property or add a value to a (to-many) relationship, and that is exactly what clang checks for.
This can be seen in the Clang source code:
/// Check for a keyword selector that starts with the word 'add' or
/// 'set'.
static bool isSetterLikeSelector(Selector sel) {
if (sel.isUnarySelector()) return false;
StringRef str = sel.getNameForSlot(0);
while (!str.empty() && str.front() == '_') str = str.substr(1);
if (str.startswith("set"))
str = str.substr(3);
else if (str.startswith("add")) {
// Specially whitelist 'addOperationWithBlock:'.
if (sel.getNumArgs() == 1 && str.startswith("addOperationWithBlock"))
return false;
str = str.substr(3);
}
else
return false;
if (str.empty()) return true;
return !islower(str.front());
}
which is called here:
/// Check a message send to see if it's likely to cause a retain cycle.
void Sema::checkRetainCycles(ObjCMessageExpr *msg) {
// Only check instance methods whose selector looks like a setter.
if (!msg->isInstanceMessage() || !isSetterLikeSelector(msg->getSelector()))
return;
/*
* rest omitted
*/
}
Your setupBar
method is not treated as "setter-like" method because "set" is not followed by an uppercase letter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With