I'm trying to write this down as concisely as possible, but it's not easy to describe -- so thanks for reading =)
I'm the main developer of the Open Source iPhone Framework Sparrow. Sparrow is modeled after the Flash AS3 Library, and thus has an event system just like AS3. Currently, that system works by specifying selectors - but I would love to expand that system by allowing the use of blocks for event listeners. However, I'm stumbling over memory management issues.
I will show you a typical use-case of events - as they are handled now.
// init-method of a display object, inheriting from
// the base event dispatcher class
- (id)init
{
if (self = [super init])
{
// the method 'addEventListener...' is defined in the base class
[self addEventListener:#selector(onAddedToStage:)
atObject:self
forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
}
return self;
}
// the corresponding event listener
- (void)onAddedToStage:(SPEvent *)event
{
[self startAnimations]; // call some method of self
}
That's quite straight-forward: When an object is added to the display list, it receives an event. Currently, the base class records the event listeners in an array of NSInvocation-objects. The NSInvocation is created in a way that it does not retain its target and arguments. (The user can make it do that, but in 99% of the cases, it's not necessary).
That these objects are not retained was a conscious choice: otherwise, the code above would cause a memory leek, even if the user removed the event listener in the dealloc-method! Here is why:
- (id)init
{
if (self = [super init])
{
// [self addEventListener: ...] would somehow cause:
[self retain]; // (A)
}
return self;
}
// the corresponding event listener
- (void)dealloc
{
// [self removeEventListener...] would cause:
[self release]; // (B)
[super dealloc];
}
On first sight, that seems fine: the retain in the init-method is paired by a release in the dealloc method. However, that does not work, since the dealloc method will never be called, because the retain count never reaches zero!
As I said, the 'addEventListener...'-method does, for exactly this reason, not retain anything in its default version. Because of the way events work (they are almost always dispatched by 'self' or child objects, which are retained anyway), that is not a problem.
However, and now we come to the central part of the question: I cannot do that with blocks. Look at the block-variant of event handling, as I would like it to have:
- (id)init
{
if (self = [super init])
{
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
{
[self startAnimations];
}];
}
return self;
}
That looks great and would be very easy to use. However: when the user calls a method on 'self' or uses a member variable within the block -- which will be, well, almost always -- the block will automatically retain 'self', and the object will never be dealloc'ed.
Now, I know that any user could rectify this by making a __block reference to self, like this:
__block id blockSelf = self;
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
{
[blockSelf startAnimations];
}];
But, honestly, I am sure almost all of the users would not know to do so or forget to do so. An API should be not only easy to use, but also hard to misuse, and this clearly violates that principle. Users of the API would most definitely misuse it.
What bugs me is that I know that 'self' does not have to be retained -- it works in my current implementation without retaining it. So I want to tell the block that he does not need to retain self -- me, the library, should tell the block that, so that the user does not have to think about it.
In my research, I have not found a way to do so. And I can't think of a way to change my architecture to fit that limitation of blocks.
Has anybody got an idea what I could do about it?
Even if you have not, thanks for reading this far -- I know it was a verbose question ;-)
I discussed this topic with Apple support, and they told me what I had expected: currently, there is no way that would let me tell the block that it should not retain self. However, they offered two ways to work around the problem.
The first would be to pass self as an argument to the block rather than a block variable. The API would become:
[self addEventListenerForType:ADDED_TO_STAGE
block:^(id selfReference, SPEvent *event)
The API would thus be responsible for passing in the (non-retained) self. Developers would still have to be told that they have to use this reference instead of self, but at least it would be easy to use.
The other solution that Apple has used in this sort of situation is to provide a separate method from release/dealloc for shutting down the listener. A good example of this is the "invalidate" method of NSTimer. It was created because of a memory cycle between NSRunLoop and NSTimer (by design).
I think the design is fundamentally flawed, blocks or no blocks. You are adding objects as event listeners before they are fully initialised. Similarly, the object could still receive events while it is being dealloced. The adding and removing of event listeners should be decoupled from allocation/deallocation IMO.
As for your immediate issue, you may not need to worry about it. You are having a problem because you are implicitly adding a reference to an object to itself (indirectly) by referencing it in the block. Bear in mind that anybody else supplying a block is unlikely to be referencing the object that generates the event or its instance variables because they won't be in scope where the block is defined. If they are (in a subclass, for instance), there's nothing you can do except document the issue.
One way to reduce the likelihood of a problem is to supply the object generating the event as a property on your SPEvent parameter and use that in the block instead of referencing self. You'll need to do that anyway because a block need not be created in a context where the object generating the event is in scope.
Related
Under ARC, compiler will forbid using any method or selector of -retainCount, -retain, -dealloc, -release, and -autorelease.
But sometimes I want to know the retain counts at runtime, or using method swizzling to swap the -dealloc method of NSObject to do something.
Is it possible to suppress (or bypass) the compiler complaining just a couple of lines of code? I don't want to modify ARC environment for whole project or whole file. I think preprocessor can do it, but how?
Additions:
Thanks guys to give me a lesson about the use of -retainCount. But I wonder whether it is possible to enforce invoking/using those forbidden methods/selectors.
I know Instruments is a powerful tool to do this job. But I am still curious about those question.
Why do I want to use -retainCount:
When using block, if you don't specify a __weak identifier on the outside variables, the block will automatically retain those outside objects in the block after the block is copied into the heap. So you need to use a weak self to avoid retain cycle, for example:
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
[weakSelf doSomething];
};
However, it will still cause retain cycle when you only use instance variables in the copied block (YES, although you didn't use any keyword self in the block).
For example, under Non-ARC:
// Current self's retain count is 1
NSLog(#"self retainCount: %d", [self retainCount]);
// Create a completion block
CompletionBlock completionBlock = ^{
// Using instance vaiable in the block will strongly retain the `self` object after copying this block into heap.
[_delegate doSomething];
};
// Current self's retain count is still 1
NSLog(#"self retainCount: %d", [self retainCount]);
// This will cuase retain cycle after copying the block.
self.completionBlock = completionBlock;
// Current self's retain count is 2 now.
NSLog(#"self retainCount: %d", [self retainCount]);
Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.
Why do I want to use -dealloc:
I want to know whether I can use method swizzling to monitor which object will be deallocated by logging messages on the Xcode console when the -dealloc is invoked. I want to replace the original implementation of -dealloc of NSObject.
That's not recommened at all, I dont know your intentions but they dont sound very safe.
The use of retainCount is not recommended.
From AppleDocs:
This method is of no value in debugging memory management issues.
Because any number of framework objects may have retained an object in
order to hold references to it, while at the same time autorelease
pools may be holding any number of deferred releases on an object, it
is very unlikely that you can get useful information from this method
And, if there's any doubt, check this link:
http://whentouseretaincount.com/
Whatever you are trying to do, please dont.
For future references, I'm going to add some linsk to help you understand the process of how memory works in iOS. Even if you use ARC, this is a must know (remember that ARC is NOT a garbage collector)
Beginning ARC in iOS 5 Tutorial Part 1
Understand memory management under ARC
Memory Management Tutorial for iOS
Advance Memory Managment
And, of course, once you understand how memory works is time to learn how to profile it with instruments:
Instruments User Guide
Agreed 100% with the other commenters about the fact that you do not want to use -retainCount. To your other question, however, about -dealloc:
You also do not want to swizzle -dealloc. If you think you want to swizzle it, you don't understand how it works. There are a lot of optimizations going on there; you can't just mess with it. But, as #bbum hints at, you can easily get notifications when objects are deallocated, and this can be very useful.
You attach an associated object to the thing you want to watch. When the thing you want to watch goes away, so does the associated object, and you can override its dealloc to perform whatever action you want. Obviously you need to be a little careful, because you're in the middle of a dealloc, but you can generally do most anything you'd need to here. Most importantly for many cases, you can put a breakpoint here or add a logging statement, so you can see where the object was released. Here's a simple example.
With ARC
const char kWatcherKey;
#interface Watcher : NSObject
#end
#import <objc/runtime.h>
#implementation Watcher
- (void)dealloc {
NSLog(#"HEY! The thing I was watching is going away!");
}
#end
NSObject *something = [NSObject new];
objc_setAssociatedObject(something, &kWatcherKey, [Watcher new],
OBJC_ASSOCIATION_RETAIN);
Without ARC
const char kWatcherKey;
#interface Watcher : NSObject
- (void)lastRetainDone;
#end
#import <objc/runtime.h>
// turn off ARC!
#implementation Watcher
{
BOOL noMoreRetainsAllowed;
}
- (void)lastRetainDone {
noMoreRetainsAllowed = YES;
}
- (id) retain {
if (noMoreRetainsAllowed) abort();
return [super retain];
}
- (void)dealloc {
NSLog(#"HEY! The thing I was watching is going away!");
[super dealloc];
}
#end
...
NSObject *something = [NSObject new];
Watcher *watcher = [Watcher new];
objc_setAssociatedObject(something, &kWatcherKey, watcher,
OBJC_ASSOCIATION_RETAIN);
[watcher lastRetainDone];
[watcher release];
Now, when something goes away, -[Watcher dealloc] will fire and log for you. Very easy. Completely supported and documented.
EDIT:
Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.
You are somewhat correct here, but there are two lessons to be learned, and neither is to use retainCount (which won't actually help you in this case anyway because retainCount can very often be something you don't expect).
The first lesson is: Do not allow any warnings in ObjC code. The situation you're describing will generally create a compiler warning in recent versions of clang. So it's actually quite easy to discover in many cases. The way you've separated it into multiple assignments, the compiler may miss it, but the lesson there is to change your coding style to help the compiler help you.
The second lesson is: don't access ivars directly outside of init and dealloc. This is one of the many little surprises that can cause. Use accessors.
My AppDelegate maintains a list of active window controllers to avoid ARC deallocating them too early. So I have a notification handler like this:
- (void) windowWillClose: (NSNotification*) notification {
[self performSelectorOnMainThread: #selector(removeWindowControllerInMainThread:)
withObject: windowController
waitUntilDone: NO];
}
- (void) removeWindowControllerInMainThread: (id) windowController {
[windowControllers removeObject: windowController];
}
I use the main thread because doing the handling on the notification thread risks deallocating the controller before it's ready.
Now, this works pretty well — except when there are animators currently running. I use animators in some places, through NSAnimationContext. I have looked at this QA, and the answer just isn't acceptable. Waiting for a while, just to get animation done, is really shoddy and not guaranteed to work; indeed it doesn't. I tried using performSelector:withObject:afterDelay, even with a larger delay than the current animation duration, and it still results in the animator running against nil objects.
What is the preferred way of doing controller cleanup like this? Not use NSAnimationContext but using NSAnimation instead, which has a stopAnimation method?
First, if some of your animations run indefinitely -- or for a very long time -- you're going to have to have a way to stop them.
But for things like implicit animations on views, you could simply use a completion method.
self.animating=YES;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
[[v animator] setAlphaValue: 1];
} completionHandler:^{
self.animating=NO;
}];
Now, you only need to poll whether your animation is running and, if it's not running, proceed to close your window.
One nice way to do the polling is to set a timer with a fixed delay. If the animation is still running, just reset the timer and wait another interval.
Alternatively, you could send a notificaton from the completion handler.
I haven't used NSAnimationContext (always did this with NSAnimation, but mostly for historical reasons). But the typical way I like to managed things similar to this is to create short-lived retain loops.
Mark's answer is exactly the right kind of idea, but the polling is not required. The fact that you reference self in the completion handler means that self cannot deallocate prior to the completion handler running. It doesn't actually matter whether you ever read animating. ARC has to keep you around until the completion block runs because the block made a reference to you.
Another similar technique is to attach yourself to the animation context using objc_setAssociatedObject. This will retain you until the completion block runs. In the completion block, remove self as an associated object, and then you'll be free to deallocate. The nice thing about that approach is that it doesn't require a bogus extra property like animating.
And of course the final, desperate measure that is occasionally appropriate is to create short-lived self-references. For instance:
- (void)setImmortal:(BOOL)imortal {
if (immortal) {
_immortalReference = self;
}
else {
_immortalReference = nil;
}
}
I'm not advocating this last option. But it's good to know that it exists, and more importantly to know why it works.
Do I take a performance hit by leaving code like the following in my app?
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
I think the answer is yes, because it results in an unnecessary method call. But I wanted to make sure.
If you are asking about an extra method call, then the answer is yes, there would be a completely unnecessary call in the chain of method invocations leading to the call of the "real" viewDidUnload.
I would not go so far as to call removal of such methods an elimination of "performance hits": it is rather unlikely that you or your end-users would notice the effects of such micro-optimization.
Very short snippet:
#pragma mark - NSNetServiceBrowserDelegate
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing
{
netService.delegate = self;
[netService resolveWithTimeout:30];
}
In this example - netServiceWillResolve: is called in the same event loop. Then, netService is released (because of ARC) and rest have no chance to be called.
I don't like the solution with ivar. NSNetService * neither array. Is there better approach to "retain" netService until last delegate method is called?
The documentation explicitly says that the delegate is not retained and that is the normal pattern in Cocoa. Also the documentation on resolveWithTimeout: says nothing about the NSNetService being retained until the delegate method has been called. Under normal memory management rules (and I think this goes for ARC too) you can't make any assumptions about the lifetime of that object outside the scope of that method.
Or to put it another way, by not maintaining a strong reference to the object, you are saying "I'm not interested in this anymore, do away with it if you like".
When observing a value on an object using addObserver:forKeyPath:options:context:, eventually you'll want to call removeObserver:forKeyPath: on that object to clean up later. Before doing that though, is it possible to check if an object actually is observing that property?
I've tried to ensure in my code that an object is only having an observer removed when it needs to be, but there are some cases where it's possible that the observer may try to remove itself twice. I'm working to prevent this, but just in case, I've just been trying to figure out if there's a way to check first if my code actually is an observer of something.
[...] is it possible to check if an object actually is observing that
property?
No. When dealing with KVO you should always have the following model in mind:
When establishing an observation you are responsible for removing that exact observation. An observation is identified by its context—therefore, the context has to be unique. When receiving notifications (and, in Lion, when removing the observer) you should always test for the context, not the path.
The best practice for handling observed objects is, to remove and establish the observation in the setter of the observed object:
static int fooObservanceContext;
- (void)setFoo:(Foo *)foo
{
[_foo removeObserver:self forKeyPath:#"bar" context:&fooObservanceContext];
_foo = foo; // or whatever ownership handling is needed.
[foo addObserver:self forKeyPath:#"bar" options:0 context:&fooObservanceContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &fooObservanceContext) {
// handle change
} else {
// not my observer callback
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
self.foo = nil; // removes observer
}
When using KVO you have to make sure that both objects, observer and observee, are alive as long as the observation is in place.
When adding an observation you have to balance this with exactly one removal of the same observation. Don't assume, you're the only one using KVO. Framework classes might use KVO for their own purposes, so always check for the context in the callback.
One final issue I'd like to point out: The observed property has to be KVO compliant. You can't just observe anything.
Part of the NSKeyValueObserving protocol is this:
- (void *)observationInfo
which should list the observers.
EDIT
Useful for debugging only.
I underestand this an objective-c question. But since lots of people use Swift/objective-c together, I thought I point out the advantage of the Swift4 new API over older versions of KVO:
If you do addObserver multiple times for KVO, then for each change you’ll get the observeValue as many as the current number of times you’ve added yourself as the observer.
And to remove yourself you have to call removeObserver as many times as you added.
Removing it more than you’ve added will result in a crash
The Swift4 observe is far smarter and swiftier!
If you do it multiple times, it doesn’t care. It won’t give multiple callbacks for each change.
And only one invalidate of the token is enough.
invalidating it before beginning to observer or more times that that you’ve done observe will not result in a crash
So to specifically answer your question, if you use the new Swift4 KVO, you don't need to care about it. Just call invalidate and you're good. But if you're using the older API then refer to Nikolai's answer