Reputation: 1401
I wrote class SafeMutableDictionary inherits NSMutableDictionary.
Class implements only "primitive" methods, which must be inherits
from NSDictionary:
- (instancetype)init;
- (instancetype)initWithObjects:(const id [])objects forKeys:(const id<NSCopying>[])keys count:(NSUInteger)cnt;
- (NSUInteger)count;
- (id)objectForKey:(id)key;
- (NSEnumerator*)keyEnumerator;
and from NSMutableDictionary:
- (void)removeObjectForKey:(id)key;
- (void)setObject:(id)obj forKey:(id)key;
Thread-safety supports by using inner variable of NSMutableDictionary type, which holds all data
@interface SafeMutableDictionary () {
__strong NSMutableDictionary* _dictEmbedded;
}
and each access to it wrap with @synchronized
block.
- (id)objectForKey:(id)key{
@synchronized (_dictEmbedded) {
return [_dictEmbedded objectForKey:key];
}
}
Repo with full code in github.
But, unfortunately, I still get crashes with errors like
Collection <__NSDictionaryM: 0x16784ff0> was mutated while being enumerated.
So, I have some questions:
1) My implementation is correct? What I missed?
2) Are exists more famous and tested solutions for this?
3) What is the best practices to access container from main and bg thread concurrently?
May be it's worst practice to do such inheritance and better using original container + care of thread-safety in
Upvotes: 1
Views: 1994
Reputation: 3955
@synchronized
assures you that you can't access an object simultaneously from several threads, but doesn't assures you that all will be ok if the situation arises.
What I did to work around this is to create a base class NBSharedObjectWithLock: With thise class, you can:
myObjetc = [[MyClassInheritingFromNBSharedObjectWithLoc] alloc] initWithMutexMode:PTHREAD_MUTEX_RECURSIVE];
[myObject lock]; // when you need exclusive access to the object
[myObject unlock]; // when exclusive access to the object is no more needed
NBSharedObjectWithLoc.h
//
// NBSharedObjectWithLock.h
// NBFoundation
//
// Created by Nicolas Buquet on 03/06/2016.
// Copyright © 2016 Nicolas Buquet. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <pthread.h>
@interface NBSharedObjectWithLock : NSObject
- (instancetype)initWithMutexMode:(int)mutexMode;
// mutexMode can be:
// - PTHREAD_MUTEX_NORMAL
// - PTHREAD_MUTEX_ERRORCHECK
// - PTHREAD_MUTEX_RECURSIVE
// - PTHREAD_MUTEX_DEFAULT ( = PTHREAD_MUTEX_NORMAL )
- (BOOL)lock;
- (BOOL)unlock;
@end
NBSharedObjectWithLoc.m
//
// NBSharedObjectWithLock.m
// NBFoundation
//
// Created by Nicolas Buquet on 03/06/2016.
// Copyright © 2016 Nicolas Buquet. All rights reserved.
//
#import "NBSharedObjectWithLock.h"
@implementation NBSharedObjectWithLock
{
pthread_mutex_t _mutexLock;
}
- (instancetype)initWithMutexMode:(int)mutexMode
{
// mutexMode can be:
// - PTHREAD_MUTEX_NORMAL
// - PTHREAD_MUTEX_ERRORCHECK
// - PTHREAD_MUTEX_RECURSIVE
// - PTHREAD_MUTEX_DEFAULT ( = PTHREAD_MUTEX_NORMAL )
self = [super init];
if (!self )
return nil;
pthread_mutexattr_t mutexAttributes;
pthread_mutexattr_init(&mutexAttributes);
pthread_mutexattr_settype(&mutexAttributes, mutexMode);
pthread_mutex_init(&_mutexLock, &mutexAttributes);
return self;
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutexLock);
[super dealloc];
}
- (BOOL)lock
{
return pthread_mutex_lock(&_mutexLock);
}
- (BOOL)unlock
{
return pthread_mutex_unlock(&_mutexLock);
}
@end
With these base class, an object can be accessed from one thread. A lock will be put on it. If another thread wants to access it, it will wait (the thread will be paused) until the lock on the object is released. Multiple objects on same thread can access the locked object if it was locked on the same thread.
The lock/unlock calls replace the @synchronize block. Be aware that you must not return between a call to lock and a call to unlock, else your object will never be unlocked. If you need to return a value:
- (NSDictionary *)dictionaryFromLockedObject
{
[myObject lock]
NSDictionary *dict = [myObject callAMethodThatReturnsADictionary];
[myObject unlock]
return dict;
}
Upvotes: 0
Reputation: 78855
There are two separate requirements here:
Multithreading safe, i.e. the collection (or dictionary in your case) remains consistent when several threads access it concurrently.
Robustness, i.e. an iterator (sometimes called enumerator) remains consistent when the collection is modified at the same time (e.g. enumerating the collection and removing selected elements the enumerator returns). This is a challenge even in a single-threaded environment.
Your solution solves requirement 1. But the error message you get is about requirement 2. NSDictionary
instances aren't robust. Very few collection impelementations (in any programming language) are.
Upvotes: 1
Reputation: 3927
See the pit-holes of using synchronised Does @synchronized guarantees for thread safety or not?
Thread Safe Solution: https://gist.github.com/steipete/5928916
Upvotes: 2