nvz.solutions
nvz.solutions

Reputation: 148

ios singleton class crashes my app

I have a problem with an singleton pattern.

I have read the following tutorials about singleton classes and have created my own. http://www.galloway.me.uk/utorials/singleton-classes/ http://www.johnwordsworth.com/2010/04/iphone-code-snippet-the-singleton-pattern/

The first time i build & run the app it works like it should. No problems at all! But when i rebuild the app the singleton class does not work properly anymore. The first init works like it should but when i call it again after a button click it crashes my app.

My singleton class:

BPManager.h

@interface BPManager : NSObject {
    NSString *dbPath;
}

@property (nonatomic, retain) NSString *dbPath;

+ (id)bpManager;
- (void)initDatabase:(NSString *)dbName;
- (int)getQuestions;

@end

BPManager.m

static BPManager *sharedMyManager = nil;

@implementation BPManager

@synthesize dbPath;

- (void)initDatabase:(NSString *)dbName
{   
    dbPath = dbName;
}
-(int)getQuestions
{
    NSLog(@"getQuestions");
}


- (id)init {
    if ((self = [super init])) {
    }
    return self;
}

+ (BPManager *) bpManager {
    @synchronized(self) {

        if(sharedMyManager != nil)  return sharedMyManager;

        static dispatch_once_t pred;        // Lock
        dispatch_once(&pred, ^{             // This code is called at most once per app
            sharedMyManager = [[BPManager alloc] init];
        });
    }

    return sharedMyManager;
}

- (void)dealloc {
    [dbPath release];
    [super dealloc];
}

When i call the following code when building my interface, the app creates the singleton:

BPManager *manager = [BPManager bpManager];
[manager initDatabase:@"database.db"];

Note: At this point i can create references to the class from other files as well. But when i click on a button it seems to loose his references.

But when a button is clicked, the following code is ecexuted:

BPManager *manager = [BPManager bpManager];
int count = [manager getQuestions];

The app should get the sharedInstance. That works, only the parameters (like dbPath) are not accessible. Why is that?

Edit:

after some research, i have changed the method to:

+ (BPManager *) bpManager {
    @synchronized(self) {

        if(sharedMyManager != nil)  return sharedMyManager;

        static dispatch_once_t pred;        // Lock
        dispatch_once(&pred, ^{             // This code is called at most once per app
            sharedMyManager = [[BPManager alloc] init];
        });
    }

    return sharedMyManager;
}

But the problem is not solved

Upvotes: 0

Views: 3828

Answers (3)

Duyen-Hoa
Duyen-Hoa

Reputation: 15784

The solution of Jano must work well. I use this way too to create singleton object. And I don't have any problem.

For your code, I think that if you use @synchronized (it's not necessary cause your have dispatch_once_t as Jano said), you should not call return in @synchronized.

+ (BPManager *) bpManager {
    @synchronized(self) {
        if(sharedMyManager == nil) {
            static dispatch_once_t pred;        // Lock
            dispatch_once(&pred, ^{             // This code is called at most once per app
                sharedMyManager = [[BPManager alloc] init];
            });
        } 
    }

    return sharedMyManager;
}

Upvotes: 0

Jano
Jano

Reputation: 63697

How about

@interface BPManager : NSObject
@property (nonatomic, copy) NSString *dbName;
@property (nonatomic, assign) int questions;
-(id) initWithDBName:(NSString*) dbName {
@end

#import "BPManager.h"
@implementation BPManager
@synthesize dbName=_dbName, questions;
+(BPManager *)singleton {
    static dispatch_once_t pred;
    static BPManager *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[BPManager alloc] initWithDBName:@"database.db"];
    });
    return shared;
}
-(id) initWithDBName:(NSString*) dbName {
    self = [super init]
    if (self) self.dbName = dbName;
    return self;
}
-(void)dealloc {   
    [_dbName release];
    [super dealloc];
}
@end

BPManager *manager = [BPManager singleton];
int count = [manager questions];

The static is private to the implementation file but no reason it should be even accessible outside the singleton method. The init overrides the default implementation with the default implementation so it's useless. In Objective-C you name the getter with the var name (count), not getCount. Initializing a class twice causes an undefined behaviour. No need to synchronize or check for if==nil when you are already using dispatch_once, see Care and Feeding of Singletons. NSString should always use copy instead retain in @property. You don't need the dealloc because this is going to be active forever while your app is running, but it's just there in case you want to use this class as a non singleton . And you probably are as good with this class being an ivar in your delegate instead a singleton, but you can have it both ways.

Upvotes: 3

Kevin
Kevin

Reputation: 56129

I'm not sure whether it's the (complete) answer, but one major flaw is that you're using instance variables (self, super) in a class method, +(id)bpManager; I'm actually surprised it let you compile that at all. Change the @synchronized(self) to @synchronized(sharedMyManager), and the [[super alloc...] init] to [[BPManager alloc...] init]. And, writing that just made me realize that the problem looks like accessing a subclassed method on an object instantiated as the superclass, but that should have been overwritten in the dispatch. Shouldn't you really only need one of those anyway, why double-init like that? (And while we're there, that's a memory leak - init'd in the if() and then overwritten in the closure without releasing it.)

Upvotes: 0

Related Questions