2

I want to have a method that will either create a new object or return an existing one based on an identifier string.

This is what I have:

@implementation MyObject {

}

@synthesize data = _data;


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

// these methods are the only ones to be used for managing the MyObject life cycle

+ (NSMutableDictionary *)objectsDict
{
    static NSMutableDictionary *map = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        map = [NSMutableDictionary new];
    });
    return map;
}

+ (MyObject *)getRefrenceForId:(NSString *)identifier
{
    return [[MyObject objectsDict] objectForKey:identifier];
}

+ (MyObject *)newRefrenceWithId:(NSString *)identifier
{
    MyObject *obj;
    @synchronized (self) {
        obj = [[MyObject objectsDict] objectForKey:identifier];

        if (obj == nil) {
            obj = [[MyObject alloc] init];
            obj.identifier = identifier;
            [[MyObject objectsDict] setObject:obj forKey:identifier];
            NSLog(@"new instance of MyObject created with id:%@",identifier);  
        }

    }
    return  obj;
}

+ (MyObject *)newRefrenceWithId:(NSString *)identifier andClassType:(Class)classType
{
    MyObject *obj;
    @synchronized (self) {
        obj = [[MyObject objectsDict] objectForKey:identifier];

        if (obj == nil) {
            obj = [[MyObject alloc] initWithClassType:classType andId:identifier];
            [[MyObject objectsDict] setObject:obj forKey:identifier];
            NSLog(@"new instance of MyObject created with id:%@ of ClassType :%@",identifier,NSStringFromClass(classType));
        }
    }
    return obj;
}

+ (void)deleteInstance:(NSString *)identifier
{
    @synchronized (self) {
        [[MyObject objectsDict] removeObjectForKey:identifier];
    }
}

+ (void)clearAllMyObjectsFromMap
{
    @synchronized (self) {
        [[MyObject objectsDict] removeAllObjects];
    }
}

Is there a better way to do this? I hear that @synchronized is very CPU expensive but GCD concurrent queues can't be used in class methods...

UPDATE: Where should the global sync queue be .. in init? That's an instance method so I doesn't work there...

user1028028
  • 6,323
  • 9
  • 34
  • 59
  • 2
    You could use a NSLock, but I think that's pretty much as CPU-expensive as your `@synchronize`. See this related answer: http://stackoverflow.com/questions/1215330/how-does-synchronized-lock-unlock-in-objective-c/1215541#1215541 – B.R.W. Jul 29 '14 at 17:46
  • So essentially there is no (much) better way to do this... – user1028028 Jul 29 '14 at 18:24
  • The GCD serial queue, or the reader-writer pattern, that CouchDeveloper outlines, can be more efficient, so if calling this a lot, it should be considered. The `@synchronized` directive enjoys certain simplicity (so if not calling it too often, it offers a certain elegance), but if you're calling this a lot, GCD is a great solution. FYI, I [benchmarked a number of synchronization techniques](http://stackoverflow.com/questions/20851332/synchronizing-read-write-access-to-an-instance-variable-for-high-performance-in/20939025#20939025). – Rob Jul 29 '14 at 21:20

1 Answers1

1

You can use GCD:

  1. Create a global "sync queue" using dispatch_once in a free function defined in module scope:

    static dispatch_queue_t get_sync_queue() {
        static dispatch_once_t onceToken;
        static dispatch_queue_t sync_queue;
        dispatch_once(&onceToken, ^{
            sync_queue = dispatch_queue_create("my.sync_queue", DISPATCH_QUEUE_CONCURRENT);
        });
        return sync_queue;
     }
    
  2. Use this queue with dispatch_sync and the block modifying your object:

    + (MyObject *)newRefrenceWithId:(NSString *)identifier
    {
        __block MyObject *obj;
        dispatch_barrier_sync(get_sync_queue(), {
            obj = [[MyObject objectsDict] objectForKey:identifier];
    
            if (obj == nil) {
                obj = [[MyObject alloc] init];
                obj.identifier = identifier;
                [[MyObject objectsDict] setObject:obj forKey:identifier];
                NSLog(@"new instance of MyObject created with id:%@",identifier);  
            }
       });
       return  obj;
    }
    

Method newRefrenceWithId: now is fully thread-safe.

Edit:

Alternatively, you can also use a concurrent sync_queue and use dispatch_barrier_sync when reading and writing in the block (as in your case)

Use dispatch_barrier_async when writing the object(s).

In case you just need to read and return the shared object's state you should use dispatch_sync - which allows for concurrent reads.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • and for deleting from the objectsDict I would use dispatch_sync ? or will I need dispatch_block_sync. – user1028028 Jul 29 '14 at 20:02
  • deleting is a "write" operation. Thus, you need to use `dispatch_barrier_async`. Important is the `barrier` here - which effectively makes this operation an _exclusive_ access, where no other operation can happen concurrently. For a write operation, `async` is also preferred (avoids dead locks, doesn't block the current thread, and optimally utilizes the queue) – CouchDeveloper Jul 29 '14 at 20:07
  • And creating isn't a write operation? I add the new object to a dictionary... Also I don't understand where I should create the global sync queue ... see updated question.. – user1028028 Jul 29 '14 at 20:18
  • Creation is also a write operation, thus you need exclusive access to the shared object, which you accomplish with `dispatch_barrier_async`. As to your second question: probably the easiest way is to use a static function (see updated answer). – CouchDeveloper Jul 29 '14 at 20:29
  • So in point 2 .. it should be dispatch_barier_async not dispatch_sync ... correct? – user1028028 Jul 29 '14 at 20:36
  • `dispatch_barrier_sync` - when the queue is a _concurrent_ queue. I changed the code using a concurrent queue. Please note: `dispatch_queue_create("my.sync_queue", DISPATCH_QUEUE_CONCURRENT)`. When using a _serial_ queue, `dispatch_barrier_(a)sync` is the same as `dispatch_(a)sync`. You need `sync` since you want to return a value which is evaluated in the block. – CouchDeveloper Jul 29 '14 at 20:44
  • @user1028028 FYI, this latter pattern is called the "reader-writer" pattern and if you're looking for Apple's discussion on the topic, it is described as the sixth asynchronous design pattern in [WWDC 2012 video #712 - Asynchronous Design Patterns with Blocks, GCD, and XPC](https://developer.apple.com/videos/wwdc/2012/?id=712). – Rob Jul 29 '14 at 21:11