开发者

how do you make a "concurrent queue safe" lazy loader (singleton manager) in objective-c

I made this class that turns any object into a singleton, but I know that it's not "concurrent queue safe." Could someone please explain to me how to do this, or better yet, show me the code. To be clear I want to know how to use this with operation queues and dispatch queues (NSOperationQueue and Grand Central Dispatch) on iOS.

Thanks in advance,

Rich

EDIT: I had an idea for how to do it. If someone could confirm it for me I'll do it and post the code. The idea is that proxies make queues all on their own. So if I make a mutable proxy (like Apple does in key-value coding/observing) for any object that it's supposed to return, and always return the same proxy for the same object/identifier pair (using the same kind of lazy loading technique as I used to create the singletons), the proxies would automatically queue up the any messages to the singletons, and make it totally thread safe.

IMHO this seems like a lot of work to do, so I don't want to do it if it's not gonna work, or if it's gonna slow my apps down to a crawl.

Here's my non-thread safe code:

RMSingletonCollector.h

//
//  RMSingletonCollector.h
//  RMSingletonCollector
//
//  Created by Rich Meade-Miller on 2/11/11.
//  Copyright 2011 Rich Meade-Miller. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "RMWeakObjectRef.h"

struct RMInitializerData {
    // The method may take one argument.
    // required
    SEL designatedInitializer;
    // data to pass to the initializer or nil.
    id data;
};
typedef struct RMInitializerData RMInitializerData;

RMInitializerData RMInitializerDataMake(SEL initializer, id data);

@interface NSObject (SingletonCollector)

// Returns the selector and data to pass to it (if the selector takes an argument) for use when initializing the singleton.
// If you override this DO NOT call super.
+ (RMInitializerData)designatedInitializerForIdentifier:(NSString *)identifier;

@end


@interface RMSingletonCollector : NSObject {

}

+ (id)collectionObjectForType:(NSString *)className identifier:(NSString *)identifier;
+ (id<RMWeakObjectReference>)referenceForObjectOfType:(NSString *)className identifier:(NSString *)identifier;

+ (void)destroyCollection;
+ (void)destroyCollectionObjectForType:(NSString *)className identifier:(NSString *)identifier;

@end

// ==--==--==--==--==Notifications==--==--==--==--==

extern NSString *const willDestroySingletonCollection;
extern NSString *const willDestroySingletonCollectionObject;

RMSingletonCollector.m

//
//  RMSingletonCollector.m
//  RMSingletonCollector
//
//  Created by Rich Meade-Miller on 2/11/11.
//  Copyright 2011 Rich Meade-Miller. All rights reserved.
//

#import "RMSingletonCollector.h"
#import <objc/objc-runtime.h>

NSString *const willDestroySingletonCollection = @"willDestroySingletonCollection";
NSString *const willDestroySingletonCollectionObject = @"willDestroySingletonCollectionObject";

RMInitializerData RMInitializerDataMake(SEL initializer, id data) {
    RMInitializerData newData;
    newData.designatedInitializer = initializer;
    newData.data = data;
    return newData;
}

@implementation NSObject (SingletonCollector)

+ (RMInitializerData)designatedInitializerForIdentifier:(NSString *)identifier {
    return RMInitializerDataMake(@selector(init), nil);
}

@end


@interface RMSingletonCollector ()

+ (NSMutableDictionary *)singletonCollection;
+ (void)setSingletonCollection:(NSMutableDictionary *)newSingletonCollection;

@end


@implementation RMSingletonCollector

static NSMutableDictionary *singletonCollection = nil;

+ (NSMutableDictionary *)singletonCollection {
    if (singletonCollection != nil) {
        return singletonCollection;
    }
    NSMutableDictionary *collection = [[NSMutableDictionary alloc] initWithCapacity:1];
    [self setSingletonCollection:collection];
    [collection release];
    return singletonCollection;
}

+ (void)setSingletonCollection:(NSMutableDictionary *)newSingletonCollection {
    if (newSingletonCollection != singletonCollection) {
        [singletonCollection release];
        singletonCollection = [newSingletonCollection retain];
    }
}

+ (id)collectionObjectForType:(NSString *)className identifier:(NSString *)identifier {
    id obj;
    NSString *key;
    if (identifier) {
        key = [className stringByAppendingFormat:@".%@", identifier];
    }
    else {
        key = className;
    }

    if (obj = [[self singletonCollection] objectForKey:key]) {
        return obj;
    }
    // dynamic creation.
    // get a class for 
    Class classForName = NSClassFromString(className);
    if (classForName) {
        obj = objc_msgSend(classForName, @selector(alloc));
        // if the initializer takes an argument...
        RMInitializerData initializerData = [classForName designatedInitializerForIdentifier:identifier];
        if (initializerData.data) {
            // pass it.
            obj = objc_msgSend(obj, initializerData.designatedInitializer, initializerData.data);
        }
        else {
            obj = objc_msgSend(obj, initializerData.designatedInitializer);
        }
        [singletonCollection setObject:obj forKey:key];
            [obj release];
    }
    else {
        // raise an exception if there is no class for the specified name.
        NSException *exception = [NSException exceptionWithName:@"com.RMDev.RMSingletonCollector.failed_to_find_class" reason:[NSString stringWithFormat:@"SingletonCollector couldn't find class for name: %@", [className description]] userInfo:nil];
        [exception raise];
        [exception release];
    }
    return obj;
}

+ (id<RMWeakObjectReference>)referenceForObjectOfType:(NSString *)className identifier:(NSString *)identifier {
    id obj = [self collectionObjectForType:className identifier:identifier];
    RMWeakObjectRef *objectRef = [[RMWeakObjectRef alloc] initWithObject:obj identifier:identifier];
    return [objectRef autorelease];
}

+ (void)destroyCollection {
    NSDictionary *userInfo = [singletonCollection copy];
    [[NSNotificationCenter defaultCenter] postNotificationName:willDestroySingletonCollection object:self userInfo:userInfo];
    [userInfo release];
    // release the collection and set it to nil.
    [self setSingletonCollection:nil];
}
+ (void)destroyCollectionObjectForType:(NSString *)className identifier:(NSString *)identifier {
    NSString *key;
    if (identifier) {
        key = [className stringByAppendingFormat:@".%@", identifier];
    }
    else {
        key = className;
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:willDestroySingletonCollectionObject object:[singletonCollection objectForKey:key] userInfo:nil];
    [singletonCollection removeObjectForKey:key];
}

@end

RMWeakObjectRef.h

//
//  RMWeakObjectRef.h
//  RMSingletonCollector
//
//  Created by Rich Meade-Miller on 2/12/11.
//  Copyright 2011 Rich Meade-Miller. All rights reserved.
//

// In order to offset the performance loss from always having to search the dictionary, I made a retainable, weak object reference class.

#import <Foundation/Foundation.h>

@protocol RMWeakObjectReference <NSObject>

@property (nonatomic, assign, readonly) id objectRef;
@property (nonatomic, retain, readonly) NSString *className;
@property (nonatomic, retain, readonly) NSString *objectIdentifier;

@end


@interface RMWeakObjectRef : NSObject <RMWeakObjectReference>
{
    id objectRef;
    NSString *className;
    NSString *objectIdentifier;
}
- (RMWeakObjectRef *)initWithObject:(id)object identifier:(NSString *)identifier;

- (void)objectWillBeDestroyed:(NSNotification *)notification;


@end

RMWeakObjectRef.m

//
//  RMWeakObjectRef.m
//  RMSingletonCollector
//
//  Created by Rich Meade-Miller on 2/12/11.
//  Copyright 2011 Rich Meade-Miller. All rights reserved.
//

#import "RMWeakObjectRef.h"
#import "RM开发者_运维百科SingletonCollector.h"

@implementation RMWeakObjectRef

@dynamic objectRef;
@synthesize className, objectIdentifier;

- (RMWeakObjectRef *)initWithObject:(id)object identifier:(NSString *)identifier {
    if (self = [super init]) {
        NSString *classNameForObject = NSStringFromClass([object class]);
        className = classNameForObject;
        objectIdentifier = identifier;
        objectRef = object;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectWillBeDestroyed:) name:willDestroySingletonCollectionObject object:object];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectWillBeDestroyed:) name:willDestroySingletonCollection object:[RMSingletonCollector class]];
    }
    return self;
}

- (id)objectRef {
    if (objectRef) {
        return objectRef;
    }
    objectRef = [RMSingletonCollector collectionObjectForType:className identifier:objectIdentifier];
    return objectRef;
}

- (void)objectWillBeDestroyed:(NSNotification *)notification {
    objectRef = nil;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [className release];
    [super dealloc];
}

@end


The easiest (and thread safe) acces to singleton:

static SomeClass* shared_instance;
+(SomeClass*) sharedInstance {
    @synchronized( shared_instance ) {
        if( !shared_instance ) {
            shared_instance = [[SomeClass alloc] init];
            //some additional initialization here
        }
    }

    return shared_instance;
}

Edit: I think particular singleton class would be your best choice. Even for lazy loading. E.g. you need to access some array of users all over your app. Then you can create singleton class:

UserManager:

+(UserManager*) sharedManager;

-(NSArray*) allUsers;
-(NSArray*) recentUsers;
-(NSArray*) featuredUsers;

-(void) addUser:(User*) user;
-(void) removeUser:(User*) user;
etc...

Then you will be able to access that arrays in every view controller. You should create singleton for other types.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜