1

I have a question on managing access to a singleton whose initialization may not be finished by this time its used the first time. I created a singleton called StoriesModel that includes some Core Data init code for opening or creating its database file. After initialization this singleton makes its ManagedObjectContext available to the various screens in the app for creating objects, displaying tables of objects, etc.

The first time that I call:

[StoriesModel sharedModel].context

it will obviously fail. In Objective C what is a good pattern to solve this problem? I know that I can just make the first call to the model class way before its used, but that won't always work. My previous languages have used things like events and data binding to solve this. I'm new to multithreading.

Here is some code for the header:

@interface StoriesModel : NSObject
+ (StoriesModel *)sharedModel;
@property (nonatomic,strong) NSManagedObjectContext *context;
@end

and the implementation:

@implementation StoriesModel

+ (StoriesModel *)sharedModel
{
    static StoriesModel *sharedSingleton;

    @synchronized(self)
    {
        if (!sharedSingleton)
        {
            sharedSingleton = [[StoriesModel alloc] init];
            [sharedSingleton doInit];
        }
        return sharedSingleton;
    }
}
- (void)doInit
{
    // do some stuff that results in the context property being set in a few seconds
}
@end

thanks,

Gerry

-- Sample modified context getter:

- (NSManagedObjectContext *)context
{
    if (!self.storyDatabase)
    {
        NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
        url = [url URLByAppendingPathComponent:@"StoryDatabase"];
        self.storyDatabase = [[UIManagedDocument alloc] initWithFileURL:url];

        // if file doesn't exist create it
        if (![[NSFileManager defaultManager] fileExistsAtPath:[self.storyDatabase.fileURL path]])
        {
            [self.storyDatabase saveToURL:_storyDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
             {
                 _context = self.storyDatabase.managedObjectContext;
                 [self populateSampleStories];
             }];
        }
        // if file exists but is closed open it
        else if (self.storyDatabase.documentState == UIDocumentStateClosed)
        {
            [self.storyDatabase openWithCompletionHandler:^(BOOL success)
             {
                 _context = self.storyDatabase.managedObjectContext;
             }];
        }
        // if file exists and is open use it
        else if (self.storyDatabase.documentState == UIDocumentStateNormal)
        {
            _context = self.storyDatabase.managedObjectContext;
        }
    }
}
Gerry Koh
  • 68
  • 1
  • 6

4 Answers4

4

You can use pthread_once or dispatch_once to ensure your singleton's initializer is executed exactly once.

dispatch_once example: Create singleton using GCD's dispatch_once in Objective C

Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226
  • Does that solve the problem though? Unless I've misunderstood the question...I took it as asking how to ensure if you're in a multi-threaded environment that you can call your singleton and guarantee it has been initialized. In theory if you were calling it off a secondary thread it could still be in the process of being initialized. `dispatch_once` will help with the initialization, but not with the potential race condition if you're doing a *lot* of threading work. – lxt Feb 28 '13 at 17:56
  • 1
    @lxt it does, and we have the same understanding of the question - say 5 threads all request the singleton using the accessor which guards initialization using one of those solutions above and initialization takes a "long" time: 4 threads will be suspended until after the initializer/block has completed. – justin Feb 28 '13 at 18:03
  • thank you, as a threading noob i could use a little more detail. i've added some sample code to the original question. Could you be more specific about where to use pthread_once or dispatch_once? It seems to me that calling the context property is a legitimate call, except that the property is nil at the time of the call. – Gerry Koh Feb 28 '13 at 18:49
  • 1
    @GerryKoh the implementation you posted will also block other threads -- the `*_once` call would just substitute the `@synchronized` mutex (and be faster). it would look exactly like the example i linked -- then you add the initialization to the block: `^{ \n sharedInstance = [[StoriesModel alloc] init]; \n [sharedInstance doInit]; \n }` and all other threads that try to access the singleton will block until after the singleton is created and the initializer has run. if your initializer takes seconds, you should consider another approach -- you shouldn't hold locks or block threads that long. – justin Feb 28 '13 at 19:23
  • i tried using dispatch_once, but i'm not sure that it solves this problem, perhaps i'm not understanding correctly. the context property can still be accessed before the singleton is done with its initialization. in my case i have a single class calling a property on the singleton and the getting back nil because the property isn't set until init is done. – Gerry Koh Feb 28 '13 at 20:11
  • @GerryKoh "the context property can still be accessed before the singleton is done with its initialization" -- it can be accessed from your initializer or something called from your initializer. other threads cannot access the instance or its property during initialization. – justin Feb 28 '13 at 20:20
  • @Gerry Koh: The problem isn't that "the property isn't set until init is done." As far as I can tell, the problem is that it isn't set *even after* init is done. Your property is asynchronously initialized in a callback in the property accessor. You need to decide if you want to be synchronous or asynchronous here. It looks like you're trying to mix the two approaches together, which won't work. – Chuck Feb 28 '13 at 20:26
  • @Chuck got it. i understand now. looks like going synchronous on the core data init stuff might be the easiest thing here. – Gerry Koh Feb 28 '13 at 21:44
2

Cocoa samurai discusses this issue in his blog, this code piece should make the singleton creation fast, and works with multithreaded apps.

  +(MyClass *)singleton {
  static dispatch_once_t pred;
  static MyClass *shared = nil;

  dispatch_once(&pred, ^{
    shared = [[MyClass alloc] init];
  });
  return shared;
  }

check out this link
http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html

Ahmad
  • 21
  • 2
  • Thats not the problem. It is that the singletons managedObjectContext context isn't instantly available – Mario Feb 28 '13 at 19:20
0

The underlying design question here is: can you do useful things while the singleton StoriesModel is being created? And will doing these complicated your code?

My preference would be to make

 [StoriesModel sharedModel].context

do the initialization and return only when it's ready.

 NSBlockOperation *op=[NSBlockOperationWithBlock:(^{
      StoryContext *context=[StoriesModel sharedModel].context;
      [context doSomething];
 };

Spin this off on another thread and proceed with your other tasks.


For example, your getter could be very simple:

- (NSManagedObjectContext *)context
{
    if (self.storyDatabase) return self.storyDatabase;
    self.storyDatabase=[self spendSomeTimeAt: theLibrary];  // slow!
    return self.storyDatabase;
}

and your initialization code could be something like this:

  NSBlockOperation *op=[NSBlockOperationWithBlock:(^{
  StoryContext *context=[StoriesModel sharedModel].context;
        [context doSomething];
  };
  NSOperatorQueue *q=[self workQueue];
  [q addOperation: op];
  // proceed to do some other things while visiting the library
  [self initializeGraphics];
  [self cook: dinner];
  [self refactor: yourCode];

But all of this smacks a bit of premature optimization. If you're not sure that setting up the context the first time is going to be a bottleneck, just do it in the straightforward way and come back to this on a rainy afternoon.

Mark Bernstein
  • 2,090
  • 18
  • 23
  • OK, I think I understand this. I added some sample code above. the trick is that the doInit method itself uses blocks to get its work done. is it legitimate in objective c to have the return statement for a getter inside a block? – Gerry Koh Feb 28 '13 at 18:54
  • Sure. In your sample code, it's not clear the doInit buys you anything though -- you could just move that code into StoryModel's init. – Mark Bernstein Feb 28 '13 at 19:33
  • I posted a sample using what i think is your approach but i can't figure out how to return the _context property from within the blocks. Im wondering if I should just stuff the core data stuff in the app delegate and have anything that cares pull the context from there. – Gerry Koh Feb 28 '13 at 20:20
0

It really depends on if you're okay with blocking the thread that's trying to access the singleton. If you are you can try using a dispatch_semaphore. Basically it lets you say "until some work is finished, block this call" (and also lets you manage a set of resources, but that's unrelated to this). Or just have a boolean done ivar and while(!done){}.

You could also have your sharedModel and doInit take a completion block or post and NSNotification.

Jack Lawrence
  • 10,664
  • 1
  • 47
  • 61