Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should delegator object lifespans be extended for the duration of a delegate callback?

I'm going to distill a very common situation to a general form. Say I'm building some library object that goes off, does some async work, then calls back to a delegate method when it's done. Now, also say for some arbitrary reason that I can't use ARC or blocks for the callback. It's old-fashioned. Let's call this object the Worker.

Now say there are several other classes across various apps that don't know anything about Worker aside from its public interface. They use the Workers to their own ends. Let's call these classes the Consumers.

Say Worker makes delegate callbacks like this:

// "Private" method called internally when work is done.
- (void)somethingFinished
{
    [self.delegate workerDidFinish];
    [self someTask];
}

And say some particular Consumer handles the callback like this:

- (void)workerDidFinish
{
    // Assume "worker" is a retain property to a Worker
    // instance that we previously created and began working,
    // and that it's also the sender of the message.
    self.worker = nil;
    // Other code...
}

Now, if nothing else has retained that particular Worker instance, we're in trouble. The Worker will be deallocated, then control will return to its -somethingFinished method, where it will then send -someTask to reclaimed or garbage memory, and likely crash. Nobody has blatantly violated any memory management rules.

I'm not asking for a technical solution here. I know several options. I'm wondering on whom the burden of responsibility for the fix falls. Should I, as the component designer, implement Worker's -somethingFinished method such that the Worker's lifespan is extended for the duration of the method with something like [[self retain] autorelease] at the beginning? Or should I, as the consumer of the component be aware that I might be blowing away an object halfway through an instance method, and wait until later to release it?

This question's answers all seem to indicate that releasing the object from the callback is a Bad Idea. Unfortunately, there's a lot of distracting information in that question about how exactly the Worker (CLLocationManager in this instance) is passed to the delegate that I've intentionally avoided here. This question is suffering the same scenario, and offers the other solution.

Personally, I can't see how the Consumer could be held responsible. It's not breaking any memory management rules, and is politely consuming the Worker's public interface. It's simply releasing an instance when it's no longer needed. But on the other hand, does that mean that any object that could possibly, somehow, be deallocated mid-method needs to artificially extend its own lifespan? Delegate methods, after all, aren't the only way a message sender could end up deallocated in the middle of a method.

So ultimately, who is responsible for the fix? Worker? Consumer? Can it be determined canonically?

like image 263
Matt Wilding Avatar asked Nov 19 '12 22:11

Matt Wilding


1 Answers

I think the burden is on the Worker in this example. The problem I see is that the Worker object is doing something internally after telling its Consumer that its work has finished. The Worker only exists for the sake of the Consumer so if the Consumer's objective is met why is Worker still doing something that has no value to the Consumer? If there are internal tasks that need to be completed after the 'consumable' work is done then those tasks are not suitably placed in an instance of the Worker object, but probably should be done by another internal object owned by a less volatile library class that won't be dealloc'd by actions of the Consumer.

like image 168
foggzilla Avatar answered Nov 15 '22 10:11

foggzilla