7

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?

Yamanqui
  • 199
  • 3
  • 10

1 Answers1

9

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

Community
  • 1
  • 1
logancautrell
  • 8,762
  • 3
  • 39
  • 50
  • I'm having troubles adapting it. I can make it work like a singleton, but then I have problems when I use `[_myController release]; _myController = nil;` and then I try to create it again, since I can't reset the `static dispatch_once_t once` variable. – Yamanqui Oct 22 '11 at 01:53
  • Hmm let me update my question with a suggestion that should work. – logancautrell Oct 22 '11 at 03:14
  • Can someone clarify my confusion about this solution? Won't this block forever if it's called from the main thread? From the concurrency programming guide: "Important - You should never call the dispatch_sync or dispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues." – Evan Grim Jun 29 '12 at 23:43
  • 1
    You are correct, this will not work if you ever access this from the main thread. The poster was apparently always using this from a background thread so it worked for them. – logancautrell Jun 30 '12 at 00:35
  • @EvanGrim: because "main thread" is not "current queue." Calling dispatch_sync from the main thread is the primary use case for GCD. However, if you dispatch a task with dispatch_sync and *that* task attempts to dispatch a task to the same queue, *bam* deadlock. – shon Aug 03 '12 at 10:39
  • Is double-checked locking safe on iOS? – CopperCash Mar 15 '19 at 01:36