3

My problem is the following: inside a method I'm creating an object like this:

MyObject* myObject = [MyObject new];

Then I want it to perform an asynchronous task like this:

[myObject doSomethingAsyncWithCompletion:^{

    //do something else upon completion
}];

This brings me to a dilemma of how to retain this object. If I don't then it will get deallocated by the end of my method and then who knows what may happen: the completion block may not be called or it may crash or anything. I would like to be sure that everything will go well.

So I have the following options:

  1. Create a strong property and assign the object to the property:

    self.myObject = myObject;

    but it seems to be ugly. Creating an additional property ONLY to retain a local object seems to be quite excessive not talking about handling hypothetic multithreading

  2. Make myObject explicitly retain itself (by capturing self in the completion block for example) but it also seems to look weird. I'm used to think that this is the responsibility of the calling code to perform all the memory management. The idea of object handling its own memory management is pretty unusual, isn't it?

So what do I do?

3 Answers 3

1
+50

There are several ways to handle this issue in Objective-C, but one common approach is to have the object retain itself while performing the asynchronous operation and then release itself when the operation is complete. This design allows the object to ensure it stays alive for the duration of the operation, independent of its creator.

Here is an example of how you might accomplish this:

@implementation MyObject

- (void)doSomethingAsyncWithCompletion:(void (^)(void))completion
{
    // Retain self
    __block MyObject *strongSelf = self;

    // Perform async operation
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // TODO: Do some actual work here

        // When done, call the completion block and release self
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) completion();
            strongSelf = nil;  // Release self
        });
    });
}

@end

In this example, strongSelf is a __block variable that retains self and ensures that the object stays alive for the duration of the operation. When the operation is complete, strongSelf is set to nil, which releases self.

Note: Be aware that this approach can potentially lead to retain cycles if you're not careful. For instance, if the object that owns the MyObject instance also is retained by a block within the MyObject instance (directly or indirectly), you could end up with a retain cycle. Always consider the entire ownership graph to avoid retain cycles.

1

An object retaining itself when it knows that it needs to continue to exist is perfectly normal.

Expecting the caller to do anything at all clever just because it is calling a method which has a completion handler is absurd and error-prone: don't do it.


The only unusual thing in your example is that you have named the method doSomethingAsyncWithCompletion. A more normal naming would not explicitly tell the programmer that the operation is always going to be performed asynchronously. doSomethingWithCompletion would be better. It allows for the possibility that some easy cases, or some obvious errors, or even a previously cached result, may be reported instantly to the completion handler before the method has even returned to its caller.


As for implementation: your method will not be asynchronous until it does something asynchronous. If that "something" is something to do with NSOperation then the block you pass to NSOperation will probably be retaining self automatically. The same probably applies to dispatch_async except that the pesky compiler will (under certain circumstances that you will remember better than me) helpfully warn you that you really want a weak reference -- which you definitely don't.

A convenient way to handle the retaining of self is to have a strong property called myself. Set myself=self early in your method code when you set up the completion handler. When your method has completed, never call the completion handler directly. Always call a single method of your own which (a) sets myself=nil and (b) calls the saved completion handler.

0

Your task holds either a strong or a weak reference to that object.

A strong reference will keep it alive until the task is finished. Even if your local variable doesn’t exist anymore.

With a weak reference you can check whether that local variable still exists. That’s useful if you can give up on the task if that object reference is gone.

For example, a view requires downloading an image. If the view is gone, you don’t need the image anymore and you can cancel the download.

2
  • Thanks for the response but it is not quite clear to me. By "task" which is holding the reference do you mean the outer context (object in which method everything is happening) or completion of doSomethingAsyncWithCompletion method? Jul 20, 2023 at 17:45
  • An example of an asynchronous task would be “download this image”. The completion would be called by the asynchronous task when it is finished, and most likely tell someone that the image has been downloaded, or that the download failed.
    – gnasher729
    Jul 28, 2023 at 10:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.