I'm using CFPropertyListCreateDeepCopy to make a deep mutable copy of an NSDictionary. This example works fine.
NSDictionary *test = #{#"1": #"One"};
NSMutableDictionary *dictionary = (__bridge NSMutableDictionary *)CFPropertyListCreateDeepCopy(NULL, (__bridge CFDictionaryRef)test, kCFPropertyListMutableContainersAndLeaves);
This doesn't work when the NSDictionary uses an NSNumber for a key value. CFPropertyListCreateDeepCopy returns nil. Here is an example.
NSDictionary *test = #{#(1): #"One"};
NSMutableDictionary *dictionary = (__bridge NSMutableDictionary *)CFPropertyListCreateDeepCopy(NULL, (__bridge CFDictionaryRef)test, kCFPropertyListMutableContainersAndLeaves);
How do I make a deep copy of an NSDictionary that has an NSNumber as a key value?
And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html#//apple_ref/doc/uid/10000048i-CH3-54303
You cannot copy a property list with a non-string key, because this is no property list.
Simply iterate over the dictionary and copy the items manually.
#implemenatation NSDictionary (DeepCopy)
- (NSMutableDictionary*)deepMutableCopy
{
NSMutableDictionary *copy = [NSMutableDictionary new];
[self enumerateKeysAndObjectsUsingBlock
^(id key, id object)
{
if([object respondsToSelector:#selector(deepMutableCopy)])
{
object = [object deepMutableCopy];
}
else if ([object respondsToSelector:#selector(mutableCopy)])
{
object = [object mutableCopy];
}
else if ([object respondsToSelector:#selector(copy)]) // Maybe, maybe not
{
object = [object copy];
}
[copy setObject:object forKey:key]
}];
return copy;
}
Do the same with NSArray.
(For such tasks I would like to have components in Objective-C.)
Related
Why NSDictionary cannot be written?? I have checked the content of the dictionary: all the instances are of NSString and NSNumber. I checked permissions: a text file with the same name at the same path is written well. Of course, my dictionary is not empty.
NSString *file = ...
NSDictionary *dict = ...
// check dictionary keys
BOOL wrong = NO;
for (id num in [dict allKeys]) {
if (![num isKindOfClass:[NSNumber class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"First");
}
// check dictionary values
wrong = NO;
for (id num in [dict allValues]) {
if (![num isKindOfClass:[NSString class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"Second");
}
if (![dict writeToFile:file atomically:YES]) {
// 0k, let's try to create a text file
NSLog(#"Names writing error!");
[#"Something here... .. ." writeToFile:file atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
Output: "Names writing error!"
Text file is created successfully.
Writing out a dictionary creates a property list, and according to the documentation all keys in a property list must be strings.
... and although NSDictionary and CFDictionary objects allow their keys to
be objects of any type, if the keys are not string objects, the
collections are not property-list objects.
NSNumber objects as keys are not supported.
As #vadian points out, you cannot write plist with numeric keys. But you can use NSKeyedArchiver:
NSURL *documents = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:nil];
NSURL *fileURL = [documents URLByAppendingPathComponent:#"test.plist"];
// this will not work
NSDictionary *dictionary = #{#1: #"foo", #2: #"bar"};
BOOL success = [dictionary writeToFile:fileURL.path atomically:true];
NSLog(#"plist %#", success ? #"success" : #"failure");
// this will
fileURL = [documents URLByAppendingPathComponent:#"test.bplist"];
success = [NSKeyedArchiver archiveRootObject:dictionary toFile:fileURL.path];
NSLog(#"archive %#", success ? #"success" : #"failure");
And you can read it back with NSKeyedUnarchiver:
// to read it back
NSDictionary *dictionary2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fileURL.path];
NSLog(#"dictionary2 = %#", dictionary2);
Note, you can do this with any class that conforms (and properly implements) NSCoding. Fortunately, NSDictionary conforms already. You have to make sure that any objects inside the dictionary, also conform (both NSString and NSNumber do). If you had a custom object in your dictionary, you'd have to make it properly conform yourself.
This is all described in the Archives and Serializations Programming Guide.
I want to see objects classes of my dictionary in console log. As for standard NSObject subclasses, I override -(NSString*) description in category:
-(NSString*) description
{
NSMutableString* desc = [NSMutableString stringWithFormat: #"<%# 0x%08x>\nobjects count: %ld", [self class], (uint)self, [self count]];
for (id key in [self allKeys])
[desc appendFormat: #"\n%# = %# (%#)", key, [self objectForKey: key], [[self objectForKey: key] class]];
return desc;
}
It works, but only for top-level NSDictionary object (if the object has dictionaries in children they are logged bypassing description method). So NSDictionary prints its children objects in some way without calling description on them...
Is there an approach to log these children dictionaries through my description method?
PS: In practical situation I want to find an object in dictionary that can't be saved to plist. Maybe there is another solution, I would be thankful for that too.
You can write a recursive description method:
// Private Methods
#interface MyClass ()
- (NSString *)_description:(id)object;
#end
...
- (NSString *)_description:(id)object
{
if ([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *dict = (NSDictionary *)object;
NSMutableString *desc = [NSMutableString stringWithFormat: #"<%# %p>\nobjects count: %ld", [dict class], dict, [dict count]];
for (id key in [dict allKeys])
{
[desc appendFormat: #"\n%# = %# (%#)", key, [self _description:[objectForKey: key]], [[self objectForKey: key] class]];
return desc;
}
}
else
{
return [(NSObject *)object description];
}
}
- (NSString *)description
{
return [self _description:self];
}
You'll probably want to pass an incrementing indentation counter so you can format the child objects better, but you should get the idea.
I want to be able to take an object and write out all its properties to a PLIST. I got so far with this:
// Get the properties of the parent class
NSMutableArray *contentViewPropertyNames = [self propertyNamesOfObject:[contentView superclass]];
// Add the properties of the content view class
[contentViewPropertyNames addObjectsFromArray:[self propertyNamesOfObject:contentView]];
// Get the values of the keys for both the parent class and the class itself
NSDictionary *keyValuesOfProperties = [contentView dictionaryWithValuesForKeys:contentViewPropertyNames];
// Write the dictionary to a PLIST
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathAndFileName = [documentsDirectory stringByAppendingPathComponent:[dataFileName stringByAppendingString:#".plist"]];
[keyValuesOfProperties writeToFile:pathAndFileName atomically:YES];
All good, except I can't write this to a PLIST because it contains some properties that are not compliant with PLISTs, so writeToFile:atomically: fails and returns NO.
Is there a good way to serialize only those properties that are serailizable into a PLIST or modify the base class of my objects to make this work?
I realise I could archive to a binary file no problem with NSCoding however I need to be able to transfer the output between a MacOS application and an iOS app, so need to go via an intermediate, platform independent format.
Of course I could be missing the point entirely, if I have please do tell, and as always, any help is useful.
Best regards
Dave
P.S.
Here is my method to get the property names of an object:
- (NSMutableArray *)propertyNamesOfObject:(id)object {
NSMutableArray *propertyNames = nil;
unsigned int count, i;
objc_property_t *properties = class_copyPropertyList([object class], &count);
if (count > 0) {
propertyNames = [[[NSMutableArray alloc] init] autorelease];
for(i = 0; i < count; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName) {
NSString *propertyName = [NSString stringWithCString:propName encoding:NSUTF8StringEncoding];
[propertyNames addObject:propertyName];
}
}
}
free(properties);
return propertyNames;
}
See if you can apply this function I recently wrote in a similar situation:
// Property list compatible types: NSString, NSData, NSArray, or NSDictionary */
- (BOOL)isPlistCompatibleDictionary:(NSDictionary *)dict {
NSSet *plistClasses = [NSSet setWithObjects:[NSString class], [NSData class],
[NSArray class], [NSDictionary class], [NSDate class],
[NSNumber class], nil];
BOOL compatible = YES;
NSArray *keys = [dict allKeys];
for (id key in keys) {
id obj = [dict objectForKey:key];
if (![plistClasses containsObject:[obj class]]) {
NSLog(#"not plist compatible: %#", [obj class]);
compatible = NO;
break;
}
}
return compatible;
}
In the below dictionary I want to write a condition for type class, Is there any way to identify the class type alone in the iteraction
NSDictionary *dictionary = [NSDictionary dictionaryWithKeysAndObjects:#"check",#"checkValue",#"webservice", [webservice class], #"list",#"listValue", nil, #"task", [task class], #"new", #"newValue", #"operation",[operation class]];
for(NSString *aKey in dictionary) {
if ([[dictionary valueForKey:aKey]) {
NSLog(#"Getting In");
}
}
Note :I want a single condition to check values [webservice class],[task class],[operation class]
Look up -isKindOfClass: and -isMemberOfClass:
if([object isKindOfClass:[AObjectClass class])
{
NSLog(#"object is of type AObjectClass");
}
See the Apple isKindOfClass: documentation.
- (BOOL)isMemberOfClass:(Class)aClass
this is what u seek probably.
For example, in this code, isMemberOfClass: would return NO:
NSMutableData *myData = [NSMutableData dataWithCapacity:30];
id anArchiver = [[NSArchiver alloc] initForWritingWithMutableData:myData];
if ([anArchiver isMemberOfClass:[NSCoder class]])
//something...
ref: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html
Edit:
In NSDictionaries you will have to put Strings for keys. I suggest you convert the class to a string with NSStringFromClass([MyClass class]); and put that as a key.
The way you want to use the class as a key is impossible.
Found answer for my question in the below link
Check if object is Class type
NSDictionary *dictionary = [NSDictionary dictionaryWithKeysAndObjects:#"check",#"checkValue",#"webservice", [webservice class], #"list",#"listValue", nil, #"task", [task class], #"new", #"newValue", #"operation",[operation class]];
for(NSString *aKey in dictionary) {
if (class_isMetaClass(object_getClass(obj))) {
NSLog(#"Getting In");
}
}
I have a method which returns me a nsdictionary with certain keys and values. i need to change the key names from the dictionary to a new key name but the values need to be same for that key,but i am stuck here.need help
This method will only work with a mutable dictionary. It doesn't check what should be done if the new key already exists.
You can get a mutable dictionary of a immutable by calling mutableCopy on it.
- (void)exchangeKey:(NSString *)aKey withKey:(NSString *)aNewKey inMutableDictionary:(NSMutableDictionary *)aDict
{
if (![aKey isEqualToString:aNewKey]) {
id objectToPreserve = [aDict objectForKey:aKey];
[aDict setObject:objectToPreserve forKey:aNewKey];
[aDict removeObjectForKey:aKey];
}
}
You can't change anything in an NSDictionary, since it is read only.
How about loop through the dictionary and create a new NSMutableDictionary with the new key names ?
Could you not add a new key-value pair using the old value, and then remove the old key-value pair?
This would only work on an NSMutableDictionary. NSDictionarys are not designed to be changed once they have been created.
To change specific key to new key, I have written a recursive method for Category Class.
- (NSMutableDictionary*)replaceKeyName:(NSString *)old_key with:(NSString )new_key {
NSMutableDictionary dict = [NSMutableDictionary dictionaryWithDictionary: self];
NSMutableArray *keys = [[dict allKeys] mutableCopy];
for (NSString key in keys) {
if ([key isEqualToString:old_key]) {
id val = [self objectForKey:key];
[dict removeObjectForKey:key];
[dict setValue:val forKey:new_key];
return dict;
} else {
const id object = [dict objectForKey: key];
if ([object isKindOfClass:[NSDictionary class]]) {
[dict setObject:[dict replaceKeyName:old_key with:new_key] forKey:key];
} else if ([object isKindOfClass:[NSArray class]]){
if (object && [(NSArray)object count] > 0) {
NSMutableArray *arr_temp = [[NSMutableArray alloc] init];
for (NSDictionary *temp_dict in object) {
NSDictionary *temp = [temp_dict replaceKeyName:old_key with:new_key];
[arr_temp addObject:temp];
}
[dict setValue:arr_temp forKey:key];
}
}
}
}
return dict;
}