Callback selector "may cause a leak"

There are many situations where a class may want to support a callback selector, to notify another class of some event. Normally, it would be implemented like:


@interface SomeClass
@property (nonatomic, weak) callbackTarget;
@property (nonatomic, assign) callbackSelector;
@end


This would be the most convenient form from the caller's perspective because it can freely set any selector it wants for the callback. However, when using ARC, the implementation of SomeClass would obviously do something like this:


    if(_callbackTarget && _callbackSelector)
        [_callbackTarget performSelector: _callbackSelector]


However, this produces the warning "PerformSelector may cause a leak because its selector is unknown". With ARC, the warning is technically speaking correct, because in principle the selector may decide to return an object, for whatever reason, which may then be leaked.


performSelector is no good with ARC. But what is the proper way to do this with ARC?


Apple seems to be promoting blocks for everything that would normally use callback selectors, and using a block is a common response to this question on the internets. However, if I understand correctly, this can easily cause an inadvertent recursive reference, and the objects may never be freed. In other words, if the class were like:


@interface SomeClass
@property (strong) void(^callbackBlock)();
@end


and then the calling code would be something like:


@interface CallingClass
{
    SomeClass someClass;
}
@end

@implementation CallingClass
- (void) foo
{
    someClass = [SomeClass new];
    someClass.callbackBlock = ^ { [self bar]; }
}

- (void) bar { ... }
@end


it's my understanding that there's an inadvertent recursive reference there, which will cause instances of CallingClass (and its SomeClass member) to never be freed. (CallingClass has a strong reference to an object of type SomeClass, which has a strong reference to the block, which has a strong reference to the same CallingClass object, thus forming a strong reference loop.)


If this is not the proper way of doing it, then what is?

The pattern you’re describing here is target-action. You can learn more about this in the Target-Action section of Cocoa Application Competencies for iOS.

Target-action has well-known benefits (the target gets to decide which method gets called) and well known problems (it has very weak type checking). It’s fine to use target-action as long as you understand those tradeoffs. Notably, target-action is a key feature of our primary user interface frameworks, AppKit and UIKit.

One of the specific problems with target-action is that ARC can’t guarantee that memory management is done safely. You have to assume that the action method on the target does not return an object. If you’re OK with that assumption, it’s fine to suppress this warning.

Apple seems to be promoting blocks for everything that would normally use callback selectors, and using a block is a common response to this question on the internets. However, if I understand correctly, this can easily cause an inadvertent recursive reference, and the objects may never be freed.

That’s certainly a gotcha with blocks. The best way around this is to define an ‘invalidate’ action that causes the class (

SomeClass
in your example) to release its callback blocks. How you do this depends on the nature of the callback. For example:
  • It’s fine to use a block for a network request callback because you know that the network request will eventually complete and, at that point, the class can release the callback block.

  • OTOH, if you use a block for a callback that doesn’t have an obvious lifetime, you must add an explicit invalidation API. This makes sense in some cases but it can be a pain in others.

In this respect using a callback block is no different from retaining the target in the target-action model, or retaining your delegate in the delegate model. There are plenty of examples of this in our frameworks (NSTimer, NSURLConnection, and so on), but they have to be approached with care.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Callback selector "may cause a leak"
 
 
Q