Supporting KVO when using composition to extend NSMutableDictionary - objective-c

I have an array of NSMutableDictionary objects which are displayed in a master–detail interface which has a few text fields and a bunch of check boxes. The controls are bound to the dictionary keys, accessed through an array controller's selection.
I'd like to add some logic which clears one check box when another is cleared, and restores the original value if it's rechecked in the same session. Since I need to associate storage with the dictionary, and need to add code too, I thought I'd use composition to extend NSMutableDictionary.
Here's what I've done:
I created a LibraryEntry subclass which contains an NSMutableDictionary.
I implemented forwardInvocation:, respondsToSelector:, methodSignatureForSelector:, and after some trial-and-error valueForUndefinedKey:.
I created my forwarder objects.
I left the bindings as they were.
It loads up the data just fine, but I'm guessing that KVO won't work correctly. I'm guessing the binder is calling addObserver: on the my object but I haven't implemented anything special to handle it.
I thought of simply overriding addObserver: and forwarding the message to the dictionary. But if I do that the observeValueForKey: notifications won't originate from my object (the original receiver of addObserver), but from the dictionary.
Before I tried to implement more transparent forwarding for these KVO calls, I thought ... this is getting messy. I keep reading "use composition, not subclassing" to get behavior like this. Is it just the wrong pattern for this situation? Why? Because of KVO?
It seems like I'd have cleaner results if I abandon composition and choose one of these alternatives:
Use decorators, with one instance observing each dictionary
Store the transient keys in the dictionary and have the controller remove them before saving
Dispense with the dictionary and declare properties instead
Here's my code, in case it's helpful (values is the dictionary):
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([values respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:values];
else
[super forwardInvocation:anInvocation];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ( [super respondsToSelector:aSelector] )
return YES;
else
return [values respondsToSelector:aSelector];
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) signature = [values methodSignatureForSelector:selector];
return signature;
}
-(id)valueForUndefinedKey:(NSString *)key {
return [values valueForKey:key];
}

I think with a combination of Objective-C associated storage, and some blocks, you could hook up arbitrary behaviors to a dictionary (or any other KVO compliant object) and solve your problem that way. I cooked up the following idea that implements a generic KVO-triggers-block mechanism and coded up and example that appears to do the sort of thing you want it to do, and does not involve subclassing or decorating foundation collections.
First the public interface of this mechanism:
typedef void (^KBBehavior)(id object, NSString* keyPath, id oldValue, id newValue, id userInfo);
#interface NSObject (KBKVOBehaviorObserver)
- (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath options: (NSKeyValueObservingOptions)options userInfo: (id)userInfo;
- (void)removeBehaviorForKeyPath: (NSString*)keyPath;
#end
This will allow you to attach block-based observations/behaviors to arbitrary objects. The task you describe with the checkboxes might look something like this:
- (void)testBehaviors
{
NSMutableDictionary* myModelDictionary = [NSMutableDictionary dictionary];
KBBehavior behaviorBlock = ^(id object, NSString* keyPath, id oldValue, id newValue, id userInfo)
{
NSMutableDictionary* modelDictionary = (NSMutableDictionary*)object;
NSMutableDictionary* previousValues = (NSMutableDictionary*)userInfo;
if (nil == newValue || (![newValue boolValue]))
{
// If the master is turning off, turn off the slave, but make a note of the previous value
id previousValue = [modelDictionary objectForKey: #"slaveCheckbox"];
if (previousValue)
[previousValues setObject: previousValue forKey: #"slaveCheckbox"];
else
[previousValues removeObjectForKey: #"slaveCheckbox"];
[modelDictionary setObject: newValue forKey: #"slaveCheckbox"];
}
else
{
// if the master is turning ON, restore the previous value of the slave
id prevValue = [previousValues objectForKey: #"slaveCheckbox"];
if (prevValue)
[modelDictionary setObject:prevValue forKey: #"slaveCheckbox"];
else
[modelDictionary removeObjectForKey: #"slaveCheckbox"];
}
};
// Set the state...
[myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: #"slaveCheckbox"];
[myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: #"masterCheckbox"];
// Add behavior
[myModelDictionary addBehavior: behaviorBlock forKeyPath: #"masterCheckbox" options: NSKeyValueObservingOptionNew userInfo: [NSMutableDictionary dictionary]];
// turn off the master
[myModelDictionary setObject: [NSNumber numberWithBool: NO] forKey: #"masterCheckbox"];
// we now expect the slave to be off...
NSLog(#"slaveCheckbox value: %#", [myModelDictionary objectForKey: #"slaveCheckbox"]);
// turn the master back on...
[myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: #"masterCheckbox"];
// now we expect the slave to be back on, since that was it's previous value
NSLog(#"slaveCheckbox value: %#", [myModelDictionary objectForKey: #"slaveCheckbox"]);
}
I implemented the block/KVO hook-up by creating an object to keep track of the blocks and userInfos, then have that be the KVO observer. Here's what I did:
#import <objc/runtime.h>
static void* kKVOBehaviorsKey = &kKVOBehaviorsKey;
#interface KBKVOBehaviorObserver : NSObject
{
NSMutableDictionary* mBehaviorsByKey;
NSMutableDictionary* mUserInfosByKey;
}
#end
#implementation KBKVOBehaviorObserver
- (id)init
{
if (self = [super init])
{
mBehaviorsByKey = [[NSMutableDictionary alloc] init];
mUserInfosByKey = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc
{
[mBehaviorsByKey release];
mBehaviorsByKey = nil;
[mUserInfosByKey release];
mUserInfosByKey = nil;
[super dealloc];
}
- (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath userInfo: (id)userInfo
{
#synchronized(self)
{
id copiedBlock = [[block copy] autorelease];
[mBehaviorsByKey setObject: copiedBlock forKey: keyPath];
[mUserInfosByKey setObject: userInfo forKey: keyPath];
}
}
- (void)removeBehaviorForKeyPath: (NSString*)keyPath
{
#synchronized(self)
{
[mUserInfosByKey removeObjectForKey: keyPath];
[mBehaviorsByKey removeObjectForKey: keyPath];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kKVOBehaviorsKey)
{
KBBehavior behavior = nil;
id userInfo = nil;
#synchronized(self)
{
behavior = [[[mBehaviorsByKey objectForKey: keyPath] retain] autorelease];
userInfo = [[[mUserInfosByKey objectForKey: keyPath] retain] autorelease];
}
if (behavior)
{
id oldValue = [change objectForKey: NSKeyValueChangeOldKey];
id newValue = [change objectForKey: NSKeyValueChangeNewKey];
behavior(object, keyPath, oldValue, newValue, userInfo);
}
}
}
#end
#implementation NSObject (KBKVOBehaviorObserver)
- (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath options: (NSKeyValueObservingOptions)options userInfo: (id)userInfo
{
KBKVOBehaviorObserver* obs = nil;
#synchronized(self)
{
obs = objc_getAssociatedObject(self, kKVOBehaviorsKey);
if (nil == obs)
{
obs = [[[KBKVOBehaviorObserver alloc] init] autorelease];
objc_setAssociatedObject(self, kKVOBehaviorsKey, obs, OBJC_ASSOCIATION_RETAIN);
}
}
// Put the behavior and userInfos into stuff...
[obs addBehavior: block forKeyPath: keyPath userInfo: userInfo];
// add the observation
[self addObserver: obs forKeyPath: keyPath options: options context: kKVOBehaviorsKey];
}
- (void)removeBehaviorForKeyPath: (NSString*)keyPath
{
KBKVOBehaviorObserver* obs = nil;
obs = [[objc_getAssociatedObject(self, kKVOBehaviorsKey) retain] autorelease];
// Remove the observation
[self removeObserver: obs forKeyPath: keyPath context: kKVOBehaviorsKey];
// remove the behavior
[obs removeBehaviorForKeyPath: keyPath];
}
#end
One thing that's sort of unfortunate, is that you have to remove the observations/behaviors in order to break down the transitive retain cycle between the original dictionary and the observing object, so if you don't remove the behaviors, you'll leak the collection. But overall this pattern should be useful.
Hope this helps.

Related

NSUserDefaults array not saving properly

I have a problem i cant get my head around. I have an instance of NSUserDefaults and have stored some values in there. One of these values is an array that contains the players scores (its a game) to be used in another display. After the game is finished, I update the array with the new score and reassign it to the NSUserDefaults, but for some reason it doesn't seem to be behaving correctly and in debug when i try to print the count into he console, its always 0.
Here is the code in question:
// // SAMHighScoreInputViewController.m // Tappity // // Created by Sam on 29/05/13. // Copyright (c) 2013 Sam. All rights reserved. //
#import "SAMHighScoreInputViewController.h"
#import "SAMScoreObject.h"
#interface SAMHighScoreInputViewController ()
#end
#implementation SAMHighScoreInputViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self; }
- (void)viewDidLoad {
self.defaults = [NSUserDefaults standardUserDefaults];
self.nameScoreLabel.text = [NSString stringWithFormat:#"%# - %#", [self.defaults objectForKey:#"lastName"], [self.defaults objectForKey:#"lastScore"]];
self.nameTextField.text = [self.defaults objectForKey:#"lastName"];
[super viewDidLoad]; // Do any additional setup after loading the view. }
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated. }
- (IBAction)textFieldValueChanged:(UITextField *)sender {
self.nameScoreLabel.text = [NSString stringWithFormat:#"%# - %#", self.nameTextField.text, [self.defaults objectForKey:#"lastScore"]];
}
- (IBAction)submitButtonTapped:(UIButton *)sender {
SAMScoreObject *score = [[SAMScoreObject alloc] init];
score.playerName = self.nameTextField.text;
score.score = [self.defaults integerForKey:#"lastScore"];
NSArray *scores = [self.defaults objectForKey:#"scoresArray"];
NSMutableArray *updatedArray = [scores mutableCopy];
[updatedArray addObject:score];
NSArray *updatedArrayScores = [updatedArray copy];
[self.defaults setObject:updatedArrayScores forKey:#"scoresArray"];
[self.defaults setObject:[self.defaults objectForKey:#"scoresArray"] forKey:#"scoresArray"];
[self.defaults synchronize];
NSLog(#"%#, %#", [self.defaults objectForKey:#"lastScore"], [self.defaults objectForKey:#"lastName"]);
NSLog(#"%i", [[self.defaults objectForKey:#"scoresArray"] count]);
[self dismissViewControllerAnimated:YES completion:nil];
} #end
You need to serialize your SAMScoreObject into an NSData object of some form before you can store it in the NSUserDefaults store.
From the documentation:
A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.
The fact that you're storing an NSArray object isn't good enough, because the objects in that array aren't one of the supported types.
Where is the scoresArray ever created? You should probably lazily create it when you attempt to update it. For example:
NSArray *scores = [self.defaults objectForKey:#"scoresArray"];
if(scores == nil)
{
scores = [NSArray array];
}
NSMutableArray *updatedArray = [scores mutableCopy];

NSUserDefaults with Custom object that has an array of custom objects

I have an object that I convert into NSData using an NSKeyedArchiver and then store it into NSUserDefaults. Everything gets saved correctly except for the elements of an array that the object has. All the objects in the array have conform to the NSCoder protocols (or whatever theyre called- ex. self.property = [decoder decodeObjectForKey:#"key"] and [encoder encodeObjectForKey:#"key"])
When I save the object, the elements of the array remain in the array, but their properties themselves do not get saved. i DO call the sycnrhonize method, so that is not the issue.
NOTE that all other times i save & load it is correct, it just does not save the elements of an array that belongs to an object. Do i have to save that separately?
The "current status" NSNumber is not being saved. Objective and target are being saved
import "Level.h"
#implementation Level
#synthesize objective = _objective;
#synthesize isComplete = _isComplete;
#synthesize goldReward = _goldReward;
#synthesize xpReward = _xpReward;
#synthesize missionID = _missionID;
#synthesize currentStatus = _currentStatus;
#synthesize targetName = _targetName;
#synthesize owner = _owner;
-(void)dealloc{
[super dealloc];
}
-(id)initWithMissionID:(int)number{
if (self = [super init]) {
self.currentStatus = 0;
self.isComplete = NO;
self.missionID = [NSNumber numberWithInt:number];
[self setUpMisson];
}
return self;
}
-(void)setUpMisson{
if ([self.missionID intValue] == 0) {
self.xpReward = [NSNumber numberWithInt:100];
self.goldReward = [NSNumber numberWithInt:100];
self.objective = [NSNumber numberWithInt:3];
self.targetName = #"Swordsman";
CCLOG(#"Gotta kill some swordsmen!");
}
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.objective forKey:#"objective"];
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
[encoder encodeObject:self.goldReward forKey:#"goldReward"];
[encoder encodeObject:self.xpReward forKey:#"xpReward"];
[encoder encodeObject:self.missionID forKey:#"missionID"];
[encoder encodeObject:self.currentStatus forKey:#"currentStatus"];
[encoder encodeObject:self.targetName forKey:#"targetName"];
[encoder encodeObject:self.owner forKey:#"owner"];
CCLOG(#"SAVING LEVEL");
}
-(id)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
self.objective = [[decoder decodeObjectForKey:#"objective"]retain];
self.isComplete = [[decoder decodeObjectForKey:#"isComplete"]retain];
self.goldReward = [[decoder decodeObjectForKey:#"goldReward"]retain];
self.xpReward = [[decoder decodeObjectForKey:#"xpReward"]retain];
self.missionID = [[decoder decodeObjectForKey:#"missionID"]retain];
self.targetName = [[decoder decodeObjectForKey:#"targetName"]retain];
self.owner = [[decoder decodeObjectForKey:#"owner"]retain];
CCLOG(#"LOADING LEVEL");
}
return self;
}
-(void)updateStatusForKill:(AI *)killedTarget{
CCLOG(#"WE KILLED: %# and OUR GOAL IS: %#",killedTarget.name,self.targetName);
if ([killedTarget.name isEqualToString:self.targetName]) {
[self setCurrentStatus:[NSNumber numberWithInt:[self.currentStatus intValue]+1]];
CCLOG(#"Current Status: %i Objective: %i", [self.currentStatus intValue],[self.objective intValue]);
if ([self.currentStatus intValue] == [self.objective intValue]) {
[self completeMission];
}
}
}
-(void)completeMission{
[self.owner setCoins:[NSNumber numberWithInt:[[self.owner coins]intValue] + [self.goldReward intValue]]];
[self.owner setXp:[NSNumber numberWithInt:[[self.owner xp]intValue] + [self.xpReward intValue]]];
CCLOG(#"complete");
[[self.owner missionList]removeObject:self];
}
#end
EDIT: The "owner" refers back to the object being saved. I think this is where the problem is, so I'm removing that and testing again.
EDIT: and that did nothing!
What you describe should "just work." In the encodeWithCoder method of your custom object, you would just encode the array object, and that should cause the array and it's contents to be encoded.
However, if any of the objects in the array do not support NSCoding, that will fail. My guess is that something in your array (or it's children or grandchildren) does not support NSCoding.
I ran into this problem when trying to save an array of Accounts that contain property values and another custom object. I couldn't save my data with your proposed solution because I was arbitrarily adding accounts to an array, and it wouldn't make sense to come up with unique identifiers for dynamically added accounts. I ended up nesting the NSCoding protocol:
In my AccountManager class:
- (void) saveAllAccounts {
[self deleteAllAccounts]; //Just removes whatever was previously stored there
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:accountArray] forKey:saveAccountsArrayKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
In my Account class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:token forKey:#"token"];
[aCoder encodeObject:specialID forKey:#"special ID"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deviceArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"device array"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
username = [[aDecoder decodeObjectForKey:#"username"] retain];
token = [[aDecoder decodeObjectForKey:#"token"] retain];
ecoID = [[aDecoder decodeObjectForKey:#"eco ID"] retain];
NSData *deviceArrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"device array"];
if (deviceArrayData != nil) {
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData: deviceArrayData];
if (savedArray != nil)
deviceArray = [[NSMutableArray alloc] initWithArray:savedArray];
}
}
return self;
}
In my AccountDevice class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:pairingPassword forKey:#"pairing password"];
[aCoder encodeObject:devicePin forKey:#"device pin"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
password = [[aDecoder decodeObjectForKey:#"password"] retain];
pin = [[aDecoder decodeObjectForKey:#"pin"] retain];
}
return self;
}
It might be a little buggy because I haven't finished testing it, but my preliminary tests were successful, and I think the concept is there.
It looks like you're using -encodeObject:forKey: and -decodeObjectForKey: even on values that aren't objects. For example, in your -initWithMissionID: you've got:
self.isComplete = NO;
which makes me think that complete is a BOOL property, but your -encodeObject:forKey: says:
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
I think you probably want to call -encodeBool:forKey: instead, like this:
[encoder encodeBool:self.isComplete forKey:#"isComplete"];
On the other hand, I'd really expect some sort of warning if the problem were that simple. Do you get any warnings? It's harder to infer the types of your other properties, but look at each of your properties for the same kind of problem.
found a workaround using a Unique-Id system for each mission and saving the progress for each mission separately into NSUserDefaults which are just then loaded again. Not ideal, but it works. Thanks for everyone's help!

Problems on NSArray's -valueForKey: when its item is NSDictionary

I have an array which contains items of NSDictionary, I want to transform the items to other objects, my first thought is valueForKey:, so I add a category method toMyObject for NSDictionary, and call for:
[array valueForKey:#"toMyObject"]
But it doesn't work as expect, it just returns the array of NSNulls.
Any ideas to solve this problem if I don't want to enumerate the array?
Answer to myself. The valueForKey: of dictionary overwrite the default behavior, if the dictionary doesn't have the key, it will return nil and not call the accessor method as NSObject do, as Apple document says:
If key does not start with “#”, invokes objectForKey:. If key does
start with “#”, strips the “#” and invokes [super valueForKey:] with
the rest of the key.
Since NSDictionary is a cluster class, it's not recommend to subclass to overwrite the behavior. Instead I use the method swiss like this:
#implementation NSDictionary (MyAddition)
static void swizzle(Class c, SEL orig, SEL new)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
+ (void)initialize
{
if (self == [NSDictionary class]){
swizzle([NSDictionary class],
#selector(valueForKey:),
#selector(myValueForKey:));
}
}
- (id)toMyObject
{
return toMyObject;
}
...
- (id)myValueForKey:(NSString *)key
{
// for collection operators
if ([key compare:#"#" options:0 range:NSMakeRange(0, 1)] == NSOrderedSame)
return [super valueForKey:key];
if ([key isEqualToString:#"toMyObject"])
return [self toMyObject];
return [self myValueForKey:key];
}
Now it's safe for an NSArray to call valueForKey:#"toMyObject".
One more implementation without swizzling:
#implementation NSObject (MLWValueForKey)
- (id)mlw_valueForKey:(NSString *)key {
if ([key hasPrefix:#"#"]) {
return [self valueForKey:key];
}
NSAssert(![key containsString:#":"], #"Key should be selector without arguments");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(key)];
#pragma clang diagnostic pop
}
#end
#implementation NSArray (MLWValueForKey)
- (id)mlw_valueForKey:(NSString *)key {
if ([key hasPrefix:#"#"]) {
return [self valueForKey:key];
}
NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
for (id object in self) {
[array addObject:[object mlw_valueForKey:key]];
}
return array;
}
#end

NSMutableDictionary thread safety

I have a question on thread safety while using NSMutableDictionary.
The main thread is reading data from NSMutableDictionary where:
key is NSString
value is UIImage
An asynchronous thread is writing data to above dictionary (using NSOperationQueue)
How do I make the above dictionary thread safe?
Should I make the NSMutableDictionary property atomic? Or do I need to make any additional changes?
#property(retain) NSMutableDictionary *dicNamesWithPhotos;
NSMutableDictionary isn't designed to be thread-safe data structure, and simply marking the property as atomic, doesn't ensure that the underlying data operations are actually performed atomically (in a safe manner).
To ensure that each operation is done in a safe manner, you would need to guard each operation on the dictionary with a lock:
// in initialization
self.dictionary = [[NSMutableDictionary alloc] init];
// create a lock object for the dictionary
self.dictionary_lock = [[NSLock alloc] init];
// at every access or modification:
[object.dictionary_lock lock];
[object.dictionary setObject:image forKey:name];
[object.dictionary_lock unlock];
You should consider rolling your own NSDictionary that simply delegates calls to NSMutableDictionary while holding a lock:
#interface SafeMutableDictionary : NSMutableDictionary
{
NSLock *lock;
NSMutableDictionary *underlyingDictionary;
}
#end
#implementation SafeMutableDictionary
- (id)init
{
if (self = [super init]) {
lock = [[NSLock alloc] init];
underlyingDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) dealloc
{
[lock_ release];
[underlyingDictionary release];
[super dealloc];
}
// forward all the calls with the lock held
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
[lock lock];
#try {
return [underlyingDictionary performv:sel : args];
}
#finally {
[lock unlock];
}
}
#end
Please note that because each operation requires waiting for the lock and holding it, it's not quite scalable, but it might be good enough in your case.
If you want to use a proper threaded library, you can use TransactionKit library as they have TKMutableDictionary which is a multi-threaded safe library. I personally haven't used it, and it seems that it's a work in progress library, but you might want to give it a try.
Nowadays you'd probably go for #synchronized(object) instead.
...
#synchronized(dictionary) {
[dictionary setObject:image forKey:name];
}
...
#synchronized(dictionary) {
[dictionary objectForKey:key];
}
...
#synchronized(dictionary) {
[dictionary removeObjectForKey:key];
}
No need for the NSLock object any more
after a little bit of research I want to share with you this article :
Using collection classes safely with multithreaded applications
http://developer.apple.com/library/mac/#technotes/tn2002/tn2059.html
It looks like notnoop's answer may not be a solution after all. From threading perspective it is ok, but there are some critical subtleties. I will not post here a solution but I guess that there is a good one in this article.
I have two options to using nsmutabledictionary.
One is:
NSLock* lock = [[NSLock alloc] init];
[lock lock];
[object.dictionary setObject:image forKey:name];
[lock unlock];
Two is:
//Let's assume var image, name are setup properly
dispatch_async(dispatch_get_main_queue(),
^{
[object.dictionary setObject:image forKey:name];
});
I dont know why some people want to overwrite setting and getting of mutabledictionary.
Even the answer is correct, there is an elegant and different solution:
- (id)init {
self = [super init];
if (self != nil) {
NSString *label = [NSString stringWithFormat:#"%#.isolation.%p", [self class], self];
self.isolationQueue = dispatch_queue_create([label UTF8String], NULL);
label = [NSString stringWithFormat:#"%#.work.%p", [self class], self];
self.workQueue = dispatch_queue_create([label UTF8String], NULL);
}
return self;
}
//Setter, write into NSMutableDictionary
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
//Getter, read from NSMutableDictionary
- (NSUInteger)countForKey:(NSString *)key {
__block NSUInteger count;
dispatch_sync(self.isolationQueue, ^(){
NSNumber *n = self.counts[key];
count = [n unsignedIntegerValue];
});
return count;
}
The copy is important when using thread unsafe objects, with this you could avoid the possible error because of unintended release of the variable. No need for thread safe entities.
If more queue would like to use the NSMutableDictionary declare a private queue and change the setter to:
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = #(count);
}
});
}
IMPORTANT!
You have to set an own private queue without it the dispatch_barrier_sync is just a simple dispatch_sync
Detailed explanation is in this marvelous blog article.
In some cases you might NSCache class. The documentation claims that it's thread safe:
You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
Here is article that describes quite useful tricks related to NSCache

deep mutable copy of a NSMutableDictionary

I am trying to create a deep-copy of a NSMutableDictionary and assign it to another NSMutableDictionary. The dictionary contains a bunch of arrays, each array containing names, and the key is an alphabet (the first letter of those names). So one entry in the dictionary is 'A' -> 'Adam', 'Apple'. Here's what I saw in a book, but I'm not sure if it works:
- (NSMutableDictionary *) mutableDeepCopy
{
NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity: [self count]];
NSArray *keys = [self allKeys];
for (id key in keys)
{
id oneValue = [self valueForKey:key]; // should return the array
id oneCopy = nil;
if ([oneValue respondsToSelector: #selector(mutableDeepCopy)])
{
oneCopy = [oneValue mutableDeepCopy];
}
if ([oneValue respondsToSelector:#selector(mutableCopy)])
{
oneCopy = [oneValue mutableCopy];
}
if (oneCopy == nil) // not sure if this is needed
{
oneCopy = [oneValue copy];
}
[ret setValue:oneCopy forKey:key];
//[oneCopy release];
}
return ret;
}
should the [onecopy release] be there or not?
Here's how I'm going to call this method:
self.namesForAlphabets = [self.allNames mutableDeepCopy];
Will that be ok? Or will it cause a leak? (assume that I declare self.namesForAlphabets as a property, and release it in dealloc).
Because of toll-free bridging, you can also use the CoreFoundation function CFPropertyListCreateDeepCopy:
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
Assuming all elements of the array implement the NSCoding protocol, you can do deep copies via archiving because archiving will preserve the mutability of objects.
Something like this:
id DeepCopyViaArchiving(id<NSCoding> anObject)
{
NSData* archivedData = [NSKeyedArchiver archivedDataWithRootObject:anObject];
return [[NSKeyedUnarchiver unarchiveObjectWithData:archivedData] retain];
}
This isn't particularly efficient, though.
IMPORTANT: The question (and my code below) both deal with a very specific case, in which the NSMutableDictionary contains only arrays of strings. These solutions will not work for more complex examples. For more general case solutions, see the following:
Tom Dalling's answer
dreamlax's answer
Source from yfujiki on GitHub Gist
Answer for this specific case:
Your code should work, but you will definitely need the [oneCopy release]. The new dictionary will retain the copied objects when you add them with setValue:forKey, so if you do not call [oneCopy release], all of those objects will be retained twice.
A good rule of thumb: if you alloc, retain or copy something, you must also release it.
Note: here is some sample code that would work for certain cases only. This works because your NSMutableDictionary contains only arrays of strings (no further deep copying required):
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary * ret = [[NSMutableDictionary alloc]
initWithCapacity:[self count]];
NSMutableArray * array;
for (id key in [self allKeys])
{
array = [(NSArray *)[self objectForKey:key] mutableCopy];
[ret setValue:array forKey:key];
[array release];
}
return ret;
}
Another technique that I have seen (which is not at all very efficient) is to use an NSPropertyListSerialization object to serialise your dictionary, then you de-serialise it but specify that you want mutable leaves and containers.
NSString *errorString = nil;
NSData *binData =
[NSPropertyListSerialization dataFromPropertyList:self.allNames
format:NSPropertyListBinaryFormat_v1_0
errorString:&errorString];
if (errorString) {
// Something bad happened
[errorString release];
}
self.namesForAlphabets =
[NSPropertyListSerialization propertyListFromData:binData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:NULL
errorDescription:&errorString];
if (errorString) {
// something bad happened
[errorString release];
}
Again, this is not at all efficient.
Trying to figure out by checking respondToSelector(#selector(mutableCopy)) won't give the desired results as all NSObject-based objects respond to this selector (it's part of NSObject). Instead we have to query if an object conforms to NSMutableCopying or at least NSCopying. Here's my answer based on this gist mentioned in the accepted answer:
For NSDictionary:
#implementation NSDictionary (MutableDeepCopy)
// As seen here (in the comments): https://gist.github.com/yfujiki/1664847
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count];
NSArray *keys = [self allKeys];
for(id key in keys) {
id oneValue = [self objectForKey:key];
id oneCopy = nil;
if([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if([oneValue conformsToProtocol:#protocol(NSMutableCopying)]) {
oneCopy = [oneValue mutableCopy];
} else if([oneValue conformsToProtocol:#protocol(NSCopying)]){
oneCopy = [oneValue copy];
} else {
oneCopy = oneValue;
}
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
#end
For NSArray:
#implementation NSArray (MutableDeepCopy)
- (NSMutableArray *)mutableDeepCopy
{
NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count];
for(id oneValue in self) {
id oneCopy = nil;
if([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if([oneValue conformsToProtocol:#protocol(NSMutableCopying)]) {
oneCopy = [oneValue mutableCopy];
} else if([oneValue conformsToProtocol:#protocol(NSCopying)]){
oneCopy = [oneValue copy];
} else {
oneCopy = oneValue;
}
[returnArray addObject:oneCopy];
}
return returnArray;
}
#end
Both methods have the same internal to-copy-or-not-to-copy logic and that could be extracted into a separate method but I left it like this for clarity.
For ARC - note kCFPropertyListMutableContainersAndLeaves for truly deep mutability.
NSMutableDictionary* mutableDict = (NSMutableDictionary *)
CFBridgingRelease(
CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(CFDictionaryRef)someNSDict,
kCFPropertyListMutableContainersAndLeaves));
Thought I'd update with an answer if you're using ARC.
The solution Weva has provided works just fine. Nowadays you could do it like this:
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDict, kCFPropertyListMutableContainers));
Useful answers here, but CFPropertyListCreateDeepCopy doesn't handle [NSNull null] in the data, which is pretty normal with JSON decoded data, for example.
I'm using this category:
#import <Foundation/Foundation.h>
#interface NSObject (ATMutableDeepCopy)
- (id)mutableDeepCopy;
#end
Implementation (feel free to alter / extend):
#implementation NSObject (ATMutableDeepCopy)
- (id)mutableDeepCopy
{
return [self copy];
}
#end
#pragma mark - NSDictionary
#implementation NSDictionary (ATMutableDeepCopy)
- (id)mutableDeepCopy
{
return [NSMutableDictionary dictionaryWithObjects:self.allValues.mutableDeepCopy
forKeys:self.allKeys.mutableDeepCopy];
}
#end
#pragma mark - NSArray
#implementation NSArray (ATMutableDeepCopy)
- (id)mutableDeepCopy
{
NSMutableArray *const mutableDeepCopy = [NSMutableArray new];
for (id object in self) {
[mutableDeepCopy addObject:[object mutableDeepCopy]];
}
return mutableDeepCopy;
}
#end
#pragma mark - NSNull
#implementation NSNull (ATMutableDeepCopy)
- (id)mutableDeepCopy
{
return self;
}
#end
Example extensions – strings are left as normal copies. You could override this if you want to be able to in place edit them. I only needed to monkey with a deep down dictionary for some testing, so I've not implemented that.