views:

326

answers:

2

I know Singleton pattern has been discussed so much. But because I'm not fully understand the memory management mechanism in Objective-C, so when I combined Singleton implementation with multithreading, I got a big error and cost me a whole day to resolve it.

I had a singleton object, let's call it ObjectX. I declared an object inside ObjectX that will detach a new thread, let's call that object objectWillSpawnNewThread, when I called

[[ObjectX sharedInstance].objectWillSpawnNewThread startNewThread];

The new thread could not be executed correctly, and finally I found out that I should not declare the object objectWillSpawnNewThread inside the singleton class.

Here are my questions:

  1. How does Objective-C allocate static object in the memory? Where does Objective-C allocate them(Main thread stack or somewhere else)?
  2. Why would it be failed if we spawn a new thread inside the singleton object?

I had searched the Objective-C language [ObjC.pdf] and Objective-C memory management document, maybe I missed something, but I currently I could not find any helpful information.

A: 

It's hard to say what you did wrong unless you post some code, but there's no magic involved with singleton objects. There is no suuch thing as a static object.

Question 1:

All Objective-C objects are allocated from the heap. You can declare pointers to them from any scope, but somewhere in your code, directly or indirectly, the object's class must be sent the alloc message and the resulting pointer initialised and assigned to your pointer. Your object's instance variables must be initialised in your object's init method. So one way to implement your singleton would be as follows:

// Header
@interface ObjectX : NSObject
{
    SpawnObject* objectWillSpawnNewThread;
}
+(ObjectX*) sharedInstance;

// Implementation
@implementation ObjectX

-(id) init
{
    self = [super init];
    if (self != nil)
    {
        objectWillSpawnNewThread = [[SpawnObject alloc] init];
    }
    return self;
}

+(ObjectX*) sharedInstance
{
    static ObjectX* sharedInstance;
    @synchronized([ObjectX class])
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[ObjectX alloc] init];
        }
    }
    return sharedInstance;
}
@end

Question 2:

Can't tell you the answer, unless you post your code and the error message. However, one common problem is forgetting to set up an autorelease pool inside the new thread.

JeremyP
Thank you Jeremy! I had read out the links you posted, and I will try to avoid using singleton any more in the new project.Thank you so much.
Tonny Xu
+1  A: 
  1. Learn the memory management rules. They are simple and in a week you’ll have the time investment back.

  2. Forget about singletons. More precisely, forget about singletons enforced by code. They are unnecessarily complicated and 99% of the time are simply bad design.

Read Singletons are Pathological Liars by Miško Hevery, read it carefully, read all of the related blog posts and think about it. Most of the time there is no real need for a class to enforce having a single instance. Usually you can get around the problem by creating some kind of Factory class that will create most instances for you and wire them together:

@interface Factory {
    id classYouWantJustOneInstanceOf;
}

- (id) wireMainController;

@implementation Factory

- (id) init {
    [super init];
    // No “sharedFoo” stuff necessary. Foo is a plain
    // class without singleton boilerplate, easily testable.
    classYouWantJustOneInstanceOf = [[Foo alloc] init];
    return self;
}

- (id) wireMainController {
    id controller = [[SomeClass alloc] init];
    // Dependencies set explicitly, good.
    [controller setFoo:classYouWantJustOneInstanceOf];
    return [controller autorelease];
}

@implementation UIApplicationDelegate

- (void) applicationDidFinishLauching: (UIApplication) app {
    // Now all your “singletons” will get created,
    // no funny static stuff.
    factory = [[Factory alloc] init];
    controller = [[factory wireMainController] retain];
    [window addSubview:controller.view];
    // up and running
}

- (void) dealloc {
   [controller release];
   // Now all your “singletons” will get released.
   [factory release];
   [super dealloc];
}

I know this feels a bit uneasy compared to “simple” [Foo sharedFoo], but it’s worth it, trust me.


The advantages of this solution, in case they are not apparent:

  1. No singleton boilerplate in your classes. No static shared instances, no thread synchronization, nothing.

  2. Explicit dependency management. If class A needs instance of B to do its work, you can see that from a public setter or a constructor parameter of A. No suprise dependencies inside the implementation file. And if you need to wire A differently, say for testing purposes, it’s easier when the dependencies are explicit (as opposed to calling [B sharedB]).

  3. Clear object lifecycle. No static variables, no static initialization and no lazy initialization unless you really want it. You know when objects get created and you can deallocate everything you create.

Note that this is a simplified case, so that the division between application delegate and Factory seems a bit pedantic, but in a real case it makes perfect sense (see the Principle of single responsibility).

zoul
Yeah, I planed to read the memory management document. This time turned out that I made a mistake at somewhere I should but I did not retain the object created by `NSMutableDictionary`. Again, about the singleton, you and @JeremyP are right, I will try to avoid using singleton any more in my new project.Thank you again.
Tonny Xu
I don't see what value the factory adds. It relies on the application delegate being a singleton to give you a singleton. Why not just make the application delegate the "factory"?On an unrelated note, the init method is wrong. You should always assign [super init] to self and then test if self == nil before attempting to initialise your ivars.
JeremyP
Do you know a case where `[NSObject init]` can return nil?
zoul