17

I'm trying to figure out what the recommended practice is for the following situation. Certain objects, such as CLLocationManager or MKReverseGeocoder, send their results asynchronously to a delegate callback method. Is it OK to release that CLLocationManager or MKReverseGeocoder instance (or whatever class it may be) in the callback method? The point is that you no longer need that object around, so you tell it to stop sending updates, set its delegate to nil, and release the object.

Pseudo code:

@interface SomeClass <CLLocationManagerDelegate>
...
@end

@implementation SomeClass

...

- (void)someMethod
{
    CLLocationManager* locManager = [[CLLocationManager alloc] init];
    locManager.delegate = self;
    [locManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
    // Do something with the location
    // ...

    [manager stopUpdatingLocation];
    manager.delegate = nil;
    [manager release];
}

@end

I am wondering if this usage pattern is considered to be always OK, if it's considered to be never OK, or if it depends on the class?

There is an obvious case where releasing the delegating object would go wrong and that is if it needs to do stuff after it has notified the delegate. If the delegate releases the object, its memory may get overwritten and the application crashes. (That appears to be what happens in my app with CLLocationManager in a particular circumstance, both only on the simulator. I'm trying to figure out if it is a simulator bug or if what I am doing is fundamentally flawed.)

I have been searching and I cannot find a conclusive answer to this. Does anyone have an authoritative source that can answer this question?

Hollance
  • 2,958
  • 16
  • 15
  • If you are seeing a crash - the other thing you could *try* would be to set it to *autorelease* rather than explicitly releasing it. Though, not knowing conclusively, this may just be obfuscating the problem, rather than fixing it for real... – Brad Oct 31 '10 at 19:33
  • Interesting, I hadn't considered that. I wonder which autorelease pool will pick this up, though. – Hollance Oct 31 '10 at 21:07
  • 1
    I'm not sure but somehow doing this feels wrongish. – Peer Stritzinger Oct 31 '10 at 21:50

4 Answers4

5

This is a very good question, I waited few hours in hope someone would give a sufficient answer, but because no one even replied, I'll give it a try. First I'll comment on your approach, then I try to suggest how I would go around this.

It's definitely a very bad idea to release - thus deallocate an object from its delegate. Just think about how objects (like a CLLocationManager) do call their delegates - they just call them in the middle of some method. When call to delegate is finished, code's execution comes back to a method of an object that has already been deallocated. BAM!

Let's forget for a moment about the fact that this is a bad idea. I see two options how to fix that easily. First, autorelease instead of release gives an object a little longer time spam - it'd at least survive returning from delegate. That should be sufficient for most of the cases, at least if author of API did her job well and encapsulated logic behind the main API class (in case of CLLocationManager it might be waiting for GPS to turn off...). Second option would be to delay releasing (performSelector:withObject:afterDelay: comes to mind), but that's more of a workaround for badly implemented APIs.

So if releasing it is not a good idea, then what is ?

Well, what do you really gain by releasing a CLLocationManager ? Freeing those few bytes of memory is not going to save your app from terminating when system is out of memory. Anyway, is it really only once that you need the current user's location ?

I suggest that you encapsulate tasks related to CLLocationManager into a separate class, probably even a singleton - that class would become its delegate, and it would take care of communicating with CLLocationManager and informing your application about results (probably by sending NSNotification). CLLocationManager would be released from that class's dealloc, and never as a result of a delegate callback. stopUpdatingLocation should suffice, freeing few bytes of memory - well, you can do it when your app is entering background, but as long as your app runs, freeing those few bytes do not make any significant improvement in memory consumption.

** Addition **

It's natural, and correct, for a delegate to have ownership of an object for which it acts as delegate. But the delegate should not release the object as a result of a callback. There's one exception to this though, and it is the callback telling you processing is over. As an example for this is NSURLConnection's connectionDidFinishLoading: which states in documentation "The delegate will receive no further messages". You can have a class downloading bunch of files, each with a different NSURLConnection (having your class as delegate), allocating AND releasing them as download of files progress.

CLLocationManager's behavior is different. You should have only one instance of CLLocationManager in your program. That instance is managed by some code, probably a singleton - one that could be released when application goes into background, reinitialized when it wakes up. CLLocationManager's lifespan would be the same as of its managing class, which acts as delegate as well.

Michal
  • 4,846
  • 3
  • 33
  • 25
  • All good points. I know how to work around the issue, I'm just wondering what is proper. One of the reasons I am asking this question is that certain semi-authoritative sources (such as Erica Sadun's Cookbook) do release the delegating object from the delegate callbacks in certain examples. So is it OK in certain cases (and how does one know in which cases this is), or are these examples simply wrong? – Hollance Nov 01 '10 at 06:13
  • Releasing is always bad idea. Autoreleasing, it depends - it might work, but there's no guarantee. APIs should be written in a way that a Manager class acts just as an interface between library and user's code, so user is free to release it and library can manage on its own till complete cleanup (network connections, hardware, GPS fix, whatever). Put all these doubts aside, and keep the object around, especially if it supports suspending like `stopUpdatingLocation` in `CLLocationManager`'s case. – Michal Nov 01 '10 at 08:25
  • because comment is restricted in length, I added more into the bottom of an answer. – Michal Nov 01 '10 at 10:38
  • Your answer comes close to what I would deem a satisfactory answer, but I'd still like to see some official documentation where this programming idiom is explained. Do you know of any such documents? – Hollance Nov 02 '10 at 17:44
  • Well, I don't read programming books (first and last one was ~20 years ago), and I have never seen API documentation goes as far as to explain such delicacies. I doubt it's written somewhere. This is why I'm thankful to have stackoverflow, well, and friends who are developers too. Discussing things in thread like this, or over a beer is much more valuable as reading about it on a piece of paper. – Michal Nov 02 '10 at 20:07
5

No. That pattern is always considered to be wrong. It breaks the Cocoa Memory Management Rules. The manager object was passed in as a parameter. You did not obtain it by new, alloc or copy, nor did you retain it. You therefore must not release it.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • But I *did* allocate the instance, and that's the pointer I'm getting back in the delegate method. I could have made `locManager` an instance variable and released that instead of the local `manager` variable, but that's still the same instance I'm releasing. So it doesn't change the nature of my question. – Hollance Nov 01 '10 at 06:07
  • 2
    @Hollance: Not in that scope you didn't. The rules are clear. You *must not* release the object. It was passed to you as a parameter, therefore the caller has a right to expect you **not** to do anything to make it go away. – JeremyP Nov 01 '10 at 09:18
  • Whoever down voted, please explain why. My answer is factually correct. – JeremyP Nov 01 '10 at 09:32
  • I down-voted your answer, because I disagree with your interpretation of the Rules. – Hollance Nov 01 '10 at 12:00
  • 1
    @Hollance: My interpretation of the rules is completely correct. This is evidenced by the fact that when you tried it, the application crashed. You cannot release that object in that method because you did not obtain it through new, alloc, retain or copy **in that method**. As a result you have deallocated an object while a method of that object is executing. – JeremyP Nov 01 '10 at 13:41
  • Yes, I deallocate an object while a method of that object is executing. That is perfectly valid if the rest of that method does not use instance variables from that object. My question basically was: is there some (unspoken) agreement in the design of delegates that allows for this? The memory management rules state that I should release objects that I own. Since I created that object, I own it and must release it. Your issue seems to be that the variable that holds the pointer is different; my response is that the pointer is still the same and therefore this is my object and mine to release. – Hollance Nov 01 '10 at 14:34
  • 1
    *"That is perfectly valid if the rest of that method does not use instance variables from that object."* How do you know that? Do you have the source code for CLLocationManager? And you are wrong when you say you own an object that was passed in as a parameter. You do not. End of story. – JeremyP Nov 01 '10 at 14:57
  • 1
    There's another point to the Cocoa Memory Management Rules: "A received object is normally guaranteed to remain valid within the method it was received in". Somewhere in the call stack something has received your CLLocationManager and by releasing it where you do, you violate the above quoted principle. – JeremyP Nov 01 '10 at 15:06
  • I own the object because I created it and retained it. In my actual code, I use an instance variable instead of the parameter. It doesn't make a difference. So we're debating something that isn't related to the original question at all. Nevertheless, I maintain that there is no significant difference: it doesn't matter whether I'm releasing the object that the instance variable points to or the object the parameter points to, since it is the SAME object. What matters is that I am releasing it and the question was: is it okay to release the CLLocationManager instance at this point? – Hollance Nov 01 '10 at 18:46
  • 2
    You are totally missing the point. You did not alloc, new retain or copy the object in that scope. Therefore you do not own it ("you" means "the current scope" more or less). And this is related to the question because you asked whether what you are doing is recommended practice. Going against the memory management rules is **not** recommended practice. And I don't see how you can maintain "no significant difference" in the face of crashes in your application. – JeremyP Nov 02 '10 at 09:32
  • I think we've both made our arguments. Even though we disagree on the application of the memory management rules, there seems to be some measure of agreement in the responses here that releasing a delegating object in its delegate callbacks is not recommended. I still would like to see some official documentation on this topic, but such a thing may not exist. – Hollance Nov 02 '10 at 17:38
3

Just like Michal said there is absolutely no good reason to release the manager object for memeory savings. Also, just likeJeremyP said, it would be completely incorrect pattern wise and design wise to release an object inside another function that only receives that object. It goes against all the rules.

However, what would be correct is to simply stop the updates and set the managers delegate to nil as you are already doing. So the only thing you need to remove is the [manager release] line.

My guess is that you are creating a manager in a local scope and therefore are trying to work out how to make sure the the manager gets released. The correct thing to do here would be to create a manager as an instance variable of your class and then just release it in the class dealloc.

twerdster
  • 4,977
  • 3
  • 40
  • 70
1

Another reason why your pattern is a bad idea, is that for something like a CLLocationManager you generally want to tell it to stop receiving updates if the screen goes to sleep - you can only do that if you maintain a reference somewhere that you can tell to start/stop. And if you are maintaining a reference, then you might as well fully manage it.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • True, but not entirely relevant to this question. I could have kept `locManager` around as an instance variable. But then I still would like to release the CLLocationManager once I'm done with it, after which I no longer need a reference. – Hollance Nov 01 '10 at 06:16