Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safe lazy initialization on iOS

I have a view controller that I want to lazily initialize, and once initialized, use the same copy when possible (I don't use a singleton since I do want to remove it from memory eventually), I use the getter to do so, my code look like this:

@property (retain) UIViewController *myController

...

@synthesize myController = _myController;


...


- (UIViewController *)myController
{
    if (!_myController) {                                 // Evaluation
        _myController = [[MyViewController alloc] init];  // Object Creation
    }
    return _myController;
}

This works, but it's not thread safe, and if more than one thread evaluate to true before the object is created, I'll have a memory leak. One solution I've tried is to @synchronized the code, but I'm not sure the correct way to do it.

This appears to work, (lockForMyController is a simple NSString) but it makes this section of code a lot slower:

 - (UIViewController *)myController
{
    @synchronized(self.lockForMyController){
        if (!_myController) {
            _myController = [[MyViewController alloc] init];
        }
    }
    return _myController;
}

I was wondering if there is some other way to achieve a lazy initialized, thread safe, property?

like image 273
Yamanqui Avatar asked Oct 22 '11 00:10

Yamanqui


1 Answers

This solution works

Note that this solution only works if myController is accessed on a background thread the first time. It will deadlock if called on the main thread.

You want to use gcd. The key is serialize the creation of the object, so that regardless of the threads starting the block, it will always only be created exactly once.

- (UIViewController *)myController
    if (_myController == nil) {
        dispatch_sync(dispatch_get_main_queue(), ^ { if (_myController == nil) _myController = [[MyViewController alloc] init]; });
    }
    return _myController;
}

Here, even if multiple threads execute the block, the execution of the block is serialized onto the main thread and only one MyViewController can ever be created.

You won't see a performance hit here unless the object is nil.

Since the property is implicitly atomic, that means that in the setter the value will be autoreleased. This should make it suitable for mingling with your custom getting, since it will autorelease any value changes to _myController.

http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW2

However, you still may get into a race condition where you are setting the value on one thread but accessing it on another. Any time you set the value, you probably want to make sure and do something like this:

dispatch_sync(dispatch_get_main_queue(), ^ { self.myController = {newValueOrNil} });

This will make sure to serialize your setter methods calls without having to reinvent the wheel for atomic setters, which is very hard to get right.

This solution does not work

You want to use gcd.

http://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once

See this post about singletons. I know you don't want a singleton, but this demonstrates how to use the method. You can easily adapt it.

Create singleton using GCD's dispatch_once in Objective C

like image 137
logancautrell Avatar answered Oct 05 '22 01:10

logancautrell