On my class I have two data objects: 'dataDict' (property) and 'prevDict' (instance variable) which are both NSMutableDictionary objects. In these multi-level dictionaries all the 'nodes' are themselves NSMutableDictionary objects and all the 'leaves' are NSString values.
Most of the time, these dictionaries will compare to be the same. But, when the new incoming data is different, I want to capture those changes via KVO and then save the new incoming dictionary. The recursive calls are the thing that fires off all the KVO changes.
Below is the code for recursively determining if the new incoming data (in property 'dataDict') is different than what's in the 'prevDictionary'. The initial call is:
[self postChangesFromPrevDict:prevDict usingKeyPath:#"dataDict"];
prevDict = [[NSMutableDictionary alloc] initWithDictionary:self.dataDict copyItems:YES];
If there's a change on a given leaf, it should update that in the dictionary and produce a KVO notification.
A sample 'newPath' string value could be "dataDict.group.name.points" -- in which the 'key' value is "points".
In the debugger breakpoint noted, I can see the values of 'curStr' and 'newStr' as, say, "120" and "121" with the these two values being correctly obtained from prevDict and dataDict, respectively.
If I obtain a value from a given keyPath then set it again using that SAME key path, why am I getting an NSUnknownKeyException?
I do know that all my dictionaries involved are mutable.
-(void) postChangesFromPrevDict:(NSMutableDictionary *)prevDictionary
usingKeyPath:(NSString *)keyPath
{
NSArray *keys = [prevDictionary allKeys];
for (NSString *key in keys) {
id obj = [prevDictionary objectForKey:key];
NSString *newPath = [keyPath stringByAppendingString:[NSString stringWithFormat:#".%#",key]];
if ([obj isKindOfClass:[NSDictionary class]])
[self postChangesFromPrevDict:obj usingKeyPath:newPath];
else {
NSString *curStr = obj;
NSString *newStr = [self valueForKeyPath:newPath];
//debug breakpoint
if (![newStr isEqualToString:curStr]) {
#try {
[self setValue:newStr forKeyPath:newPath];
}
#catch (NSException *__unused exception) {
NSLog(#"Can't set key on %#",newPath);
}
}
}
}
}
PS: To recurse, I test for an NSDictionary class which is fine as NSMutableDictionary is a subclass thereof.
Related
I'm getting something weird that I can't get. I'm using a thirdy-party control (https://github.com/brunow/TableKit.m) and it do:
+ (NSSet *)cellMappingsForObject:(id)object mappings:(NSDictionary *)mappings {
NSString *objectStringName = NSStringFromClass([object class]);
return [mappings objectForKey:objectStringName];
}
It's create a mapping based in the name of the class:
[TKCellMapping mappingForObjectClass:[NSMutableDictionary class] block:^(TKCellMapping *cellMapping) {
//CODE
}];
And it check the datasource objects against it mappings. The objects are build for me as:
while ([rs next]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *fieldName in [props allKeys]) {
//CODE
[dict setValue:fieldValue forKey:fieldName];
}
[list addObject :dict]; //<--This is the datasource. The map is against each dict
}
So, everywhere I'm telling it is NSMutableDictionary. But then this weird thing happend.
When it calls NSStringFromClass([NSMutableDictionary class]) it say NSMutableDictionary but when it calls NSStringFromClass([rowDict class]) it say __NSDictionaryM!
Why happend this? I check for the header of __NSDictionaryM and Xcode navigate to NSMutableDictionary (?). I can't create objects from __NSDictionaryM.
BTW, what I need to do for replace NSStringFromClass to get exactly NSMutableDictionary?
It's a private subclass of NSMutableDictionary. It is an NSMutableDictionary. Read more about class clusters here.
i've done it with a subclass check:
if( [[o class] isSubclassOfClass:[NSMutableDictionary class]] ) {
// o is at least a subclass of NSMutableDictionary
}
I'm writing a recursive method that basically traverses a NSManagedObject object and converts it to a JSON dictionary. I have the bulk of this done, but I am running into an issue where the method goes into an infinite loop when it comes to an object's inverse.
For example, let's say the method starts off with an object that has a class of Job, and inside of that Job object there is a property called surveys. The surveys is an NSSet that contains multiple JobSurvey objects. Each JobSurvey object contains an inverse back to the original Job object and the property is called "Job".
When I run this through my method it starts the infinite loop by going into the job and starts to process each property. Once the method gets to the surveys property, it'll be called again to process each JobSurvey object as expected. The method then processes each property until it reaches the Job (inverse) object. At that time it'll continue to process that object, and thus creates the infinite loop.
Any thoughts on how I can fix this? I'm trying to write this method without having to create custom object classes with object mapping as it needs to be able to be used with any type of object I pass into it. Below is the code I have thus far.
- (NSDictionary *)encodeObjectsForJSON:(id)object
{
NSMutableDictionary *returnDictionary = [[NSMutableDictionary alloc] init];
// get the property list for the object
NSDictionary *props = [VS_PropertyUtilities classPropsFor:[object class]];
for (NSString *key in props) {
// get the value for the property from the object
id value = [object valueForKey:key];
// if the value is just null, then set a NSNull object
if (!value) {
NSNull *nullObj = [[NSNull alloc] init];
[returnDictionary setObject:nullObj forKey:key];
// if the value is an array or set, then iterate through the array or set and call this method again to encode it's values
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSSet class]]) {
NSMutableArray *retDicts = [[NSMutableArray alloc] init];
// encode each member of the array to a JSON dictionary
for (id val in value) {
[retDicts addObject:[self encodeObjectsForJSON:val]];
}
// add to the return dictionary
[returnDictionary setObject:retDicts forKey:key];
// else if this is a foundation object, then set it to the dictionary
} else if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSNull class]] || [value isKindOfClass:[NSDate class]]) {
[returnDictionary setObject:value forKey:key];
// else this must be a custom object, so call this method again with the value to try and encode it
} else {
NSDictionary *retDict = [self encodeObjectsForJSON:value ];
[returnDictionary setObject:retDict forKey:key];
}
}
return returnDictionary;
}
This is what I did. It is similar to what you are trying to achieve.
link to da codes
It is a bit more explicit than i think you are looking for but I thought I'd suggest.
Say I have NSMutableArray *array1 with 10 objects. I want to create an *array2 and add 5 objects from array1 to array2, and I want it so that when I change these object properties from array2, they also change the 5 specific objects from array1 as well. How would I do this?
Edit: Ok I think I asked the wrong question. It's more about passing by reference and pointers, which I confuse too much:
NSMutableArray *mainArray;
NSMutableArray *secondaryArray;
NSMutableDictionary *dic1;
[mainArray addObject:dic1];
[self changeValues:[mainArray lastObject]];
-(void)changeValues:(NSMutableDictionary*)someDic
{
[secondaryArray addObject:someDic];
NSMutableDictionary *aDic=[secondaryArray lastObject];
...//some code to change values of aDic
//by changing aDic, I want to also change the same dic from mainArray
//so [mainArray lastObject] should be the same exact thing as [secondaryArray lastObject]
}
How would I change the above code so the changes reflect in both arrays?
NSMutableArray *array2 = [NSMutableArray array];
for (int i=0; i<5; ++i){
[array2 addObject: [array1 objectAtIndex:i] ]
}
In this example you have the set of objects pointed by items of array1 as well as by items
of array2, since NSMutableArray contains pointers to objects, not objects theirselves.
Therefore, changing the object thru pointer in one array you may observe that change thru
pointer from other array.
Edit
#mohabitar, you already receive an answers. dic1, someDic and aDic - all these values are same. Just change aDic(or someDic) and see result.
This sounds like a good case for some KVC (Key-Value Coding).
With KVC you can create indexed properties and have the KVC engine create an array proxy for the indexed property, which will then allow you to operate on the indexed property as if it were an array.
Below is a quick proof-of-concept piece of code, tested on both OS X and iOS.
Interface:
#property (strong) NSMutableArray *mainArray;
Implementation:
#synthesize mainArray = _mainArray;
- (id)init
{
self = [super init];
if (self) {
// For simplicity, use strings as the example
_mainArray = [NSMutableArray arrayWithObjects:
#"1st element",
#"2nd element",
#"3rd element",
#"4th element",
#"5th element",
#"6th element",
#"7th element",
#"8th element",
#"9th element",
#"10th element",
nil];
}
return self;
}
// KVC for a synthetic array, accessible as property #"secondaryArray"
- (NSUInteger) countOfSecondaryArray
{
return 5;
}
- (id) objectInSecondaryArrayAtIndex: (NSUInteger) index
{
// In practice you would need your mapping code here. For now
// we just map through a plain C array:
static NSUInteger mainToSecondaryMap[5] = {1,4,5,7,8};
return [self.mainArray objectAtIndex:mainToSecondaryMap[index]];
}
- (void) watchItWork
{
NSArray *secondaryArray = [self valueForKey:#"secondaryArray"];
// See how the sub array contains the elements from the main array:
NSLog(#"%#", secondaryArray);
// Now change the main array and watch the change reflect in the sub array:
[self.mainArray replaceObjectAtIndex:4 withObject:#"New String"];
NSLog(#"%#", secondaryArray);
}
There is more information in the docs, specifically the part on Indexed Accessor Pattern.
Shortly: how to determine if id is CFType or not at runtime
i'm implementing dynamic core data attributes and in willSave method of ExtendedManagedObject i wanna check if the id value is CFType to store it into plist file.
If I'm trying to save to plist UIImage, that is not toll-free bridged with CF (apple docs), I am getting an error:
2011-11-17 17:16:25.294 [490:707] Error saving extended data: Property list invalid for format (property lists cannot contain objects of type 'CFType')
Can I check it with some method or I have to implement by myself (just isKindOfClass from docs)?
I don't want to implement accessors in NSManagedObject subclass, I dont know exactly how many urls I'll get from entities properties. Question is about dynamic extended attributes at runtime.
- (void)willSave
{
NSDictionary *changes = [self valueForKey:#"extendedChanges"];
if (changes!=nil) {
// merge changes into snapshot
NSMutableDictionary *dict = [[self extendedSnapshot] mutableCopy];
NSEnumerator *e = [changes keyEnumerator];
NSString *key;
while (key=[e nextObject]) {
id value = [changes objectForKey:key];
if (value==[NSNull null])
[dict removeObjectForKey:key];
else if (#ugly and I'm not shure is thread safe **else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]] || [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])**)
[dict setObject:value forKey:key];
}
// archive as binary plist
NSData *data = nil;
if ([dict count]>0) {
NSString *error=nil;
data = [NSPropertyListSerialization dataFromPropertyList:dict
format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
if (error!=nil) {
NSLog(#"Error saving extended data: %#", error);
[error release];
}
}
[dict release];
[self setPrimitiveValue:data forKey:#"extendedData"];
}
[super willSave];
}
That's a wrong way to approach the problem. The blog post you referenced saves the extended attributes as a serialized plist. The plist can contain only the following types of objects, as written in the official doc:
NSArray, NSDictionary, NSDate, NSData, NSString and NSNumber.
Other classes are just not allowed. Don't add objects of any other class to the attributes extended this way.
Also, saving an image file in a CoreData database is not a good idea, generally speaking, read On Blobs in the official doc. Instead, save the UIImage in a file and write the file path in the CoreData database.
If you just have a transient property which is not saved to the database, you don't even have to go through the trouble of creating extended attributes. Just add it as a property of a subclass of NSManagedObject:
#interface MyManagedObject: NSManagedObject {
}
#property(retain, nonatomic) UIImage* thumbnail;
#property(retain, nonatomic) NSDictionary* thumbnailDictionary;
#end
without adding thumbnail in the CoreData model. Then do the corresponding #synthesize in the .m file. The property added this way to a managed object is just not saved.
If you want to keep unknown number of thumbnails, you can put an NSDictionary (or NSArray) containing UIImage*s.
If you really do want to get CFType from an NSObject*, do the following:
NSObject* x= ...;
CFTypeID typeID=CFGetTypeID((CFTypeRef)x);
if(typeID != CFStringGetTypeID() &&
typeID != CFDateGetTypeID() &&
typeID != CFDataGetTypeID() &&
typeID != CFDictionaryGetTypeID() &&
typeID != CFArrayGetTypeID() ) {
... it's a non-plistable type ...
}
The ugly if clause is there because there's no public function which gives you the CFTypeID of an un-bridged Objective-C object.
Read CFType reference. But I don't recommend doing this.
Trying to store an image in UIImage format also results in "property lists cannot contain objects of type CFType error. Best way is to convert UIImage to NSData before writing to pList.
I'm parsing some input which produces a tree structure containing NSDictionary instances on the branches and NSString instance at the nodes.
After parsing, the whole structure should be immutable. I feel like I'm jumping through hoops to create the structure and then make sure it's immutable when it's returned from my method.
We can probably all relate to the input I'm parsing, since it's a query string from a URL. In a string like this:
a=foo&b=bar&a=zip
We expect a structure like this:
NSDictionary {
"a" => NSDictionary {
0 => "foo",
1 => "zip"
},
"b" => "bar"
}
I'm keeping it just two-dimensional in this example for brevity, though in the real-world we sometimes see var[key1][key2]=value&var[key1][key3]=value2 type structures. The code hasn't evolved that far just yet.
Currently I do this:
- (NSDictionary *)parseQuery:(NSString *)queryString {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *pairs = [queryString componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSRange eqRange = [pair rangeOfString:#"="];
NSString *key;
id value;
// If the parameter is a key without a specified value
if (eqRange.location == NSNotFound) {
key = [pair stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
value = #"";
} else {
// Else determine both key and value
key = [[pair substringToIndex:eqRange.location] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if ([pair length] > eqRange.location + 1) {
value = [[pair substringFromIndex:eqRange.location + 1] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else {
value = #"";
}
}
// Parameter already exists, it must be a dictionary
if (nil != [params objectForKey:key]) {
id existingValue = [params objectForKey:key];
if (![existingValue isKindOfClass:[NSDictionary class]]) {
value = [NSDictionary dictionaryWithObjectsAndKeys:existingValue, [NSNumber numberWithInt:0], value, [NSNumber numberWithInt:1], nil];
} else {
// FIXME: There must be a more elegant way to build a nested dictionary where the end result is immutable?
NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:existingValue];
[newValue setObject:value forKey:[NSNumber numberWithInt:[newValue count]]];
value = [NSDictionary dictionaryWithDictionary:newValue];
}
}
[params setObject:value forKey:key];
}
return [NSDictionary dictionaryWithDictionary:params];
}
If you look at the bit where I've added FIXME it feels awfully clumsy, pulling out the existing dictionary, creating an immutable version of it, adding the new value, then creating an immutable dictionary from that to set back in place. Expensive and unnecessary?
I'm not sure if there are any Cocoa-specific design patterns I can follow here?
Expensive and unnecessary?
Yes. Apple's Cocoa APIs regularly say they return an immutable object, but actually return a mutable subclass that's been cast to the immutable version. This is a standard operating procedure and an accepted Cocoa design principle. You just trust that your clients aren't going to cast it back to a mutable version and change things from underneath you.
From Cocoa Core Competencies: Object Mutability:
Receiving Mutable Objects
When you call a method and receive an object in return, the object could be mutable even if the method’s return type characterizes it as immutable. There is nothing to prevent a class from declaring a method to return an immutable object but returning a mutable object in its implementation. Although you could use introspection to determine whether a received object is actually mutable or immutable, you shouldn’t. Always use the return type of an object to judge its mutability.
See also: Cocoa Fundamentals Guide: Cocoa Objects.