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.
Related
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.
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.
Is it better to create a local object for later use like
NSDictionary *dic = [NSDictionary Dictionary];
or
NSDictionary * dic = nil;
Is it preference thing or is one better then the other?
it's not like 'the one is better', it's like 'the other is bad'.
If you're going to assign a new object to it later, initialize it to nil, else (you leak memory by losing the reference to the first object created by error.) - EDIT: no, you're not leaking memory (either because of the autorelease or the automatic reference counting, but anyway, that's an extra unneeded method call.) That is bad.
If it's a mutable collection, create it before you use it, else it will continue being nil and ignoring essentially all messages sent to it, which is also bad.
Conclusion: it's not a matter of preference - you must think logically and choose whichever is suited for the specific purpose you are using it for.
If you will use that object later, then you should instantiate it with the first option. If you will have to create an object in some if-else block where you will be reinitializing it with some custom values, then the second option is the way to go.
For example the first option:
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
[arr addObject:#"string"];
}
or
NSDictionary *dictionary = nil;
BOOL flag;
if (flag) {
dictionary = [NSDictionary dictionaryWithObject:#"string" forKey:#"myKey"];
}
else {
NSArray *objects;
NSArray *keys;
dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}
myArray is an NSArray of NSDictionary objects. Will [myArray mutableCopy] contain the original NSDictionary objects or NSMutableDictionary copies? Is there a simple way to make an immutable object graph completely mutable?
If you don't mind the amount of time required for a large object graph and you actually want deep copies of objects, you could serialize your object graph and then deserialize it. The easiest way to do this (assuming all your objects are Foundation Collection Objects) is to use the NSPropertyListSerialization class. Serialize your root object to data, then deserialize to your mutable root-level array using the NSPropertyListMutableContainersAndLeaves option. Your resulting root-level mutable array will be a deep-copy and all containers will be mutable. It's important to remember that this really will be a deep-copy, so if you change something in another container, that change won't be reflected in the original objects.
Here is a quick code example:
// Assumes the root-level object is an array, adjust as necessary
- (NSMutableArray*)deepMutableCopyOfArray:(NSArray*)array error:(NSError**)outError
{
NSError* error = nil;
NSData* serializedData = [NSPropertyListSerialization dataWithPropertyList:array format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if( !serializedData ) {
if( outError ) *outError = error;
return nil;
}
NSMutableArray* mutableCopy = [[NSPropertyListSerialization propertyListWithData:serializedData options:NSPropertyListMutableContainersAndLeaves format:NULL error:&error] retain];
if( !mutableCopy ) {
if( outError ) *outError = error;
return nil;
}
return mutableCopy;
}
Copies in Cocoa are generally shallow. This means that it only affects the top most object, in this case the array. You'll end up with a mutable array of immutable dictionaries. There is no one liner to make the entire thing mutable like you're asking.
The only way to do this is to iterate through the original array, create mutable copies of each object and replace the immutable objects in the mutable array with their mutable brethren. Whew, the word mutable has lost all meaning.
NSArray *mutableArray = [originalArray mutableCopy];
for (NSDictionary *dictionary in originalArray)
{
NSInteger index = [originalArray indexOfObject:dictionary];
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
[mutableArray replaceObjectAtIndex:index withObject:mutableDictionary];
}
It should be clear that you can work down even further into the graph with nested for loops. Depending on the size of the array, you may require an autorelease pool to keep memory in check.
If you can't get an object with objectAtIndex: from an NSSet then how do you retrieve objects?
There are several use cases for a set. You could enumerate through (e.g. with enumerateObjectsUsingBlock or NSFastEnumeration), call containsObject to test for membership, use anyObject to get a member (not random), or convert it to an array (in no particular order) with allObjects.
A set is appropriate when you don't want duplicates, don't care about order, and want fast membership testing.
NSSet doesn't have a method objectAtIndex:
Try calling allObjects which returns an NSArray of all the objects.
it is possible to use filteredSetUsingPredicate if you have some kind of unique identifier to select the object you need.
First create the predicate (assuming your unique id in the object is called "identifier" and it is an NSString):
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"identifier == %#", identifier];
And then choose the object using the predicate:
NSObject *myChosenObject = [mySet filteredSetUsingPredicate:myPredicate].anyObject;
NSArray *myArray = [myNSSet allObjects];
MyObject *object = [myArray objectAtIndex:(NSUInteger *)]
replace NSUInteger with the index of your desired object.
For Swift3 & iOS10 :
//your current set
let mySet : NSSet
//targetted index
let index : Int
//get object in set at index
let object = mySet.allObjects[index]
NSSet uses the method isEqual: (which the objects you put into that set must override, in addition, the hash method) to determine if an object is inside of it.
So, for example if you have a data model that defines its uniqueness by an id value (say the property is:
#property NSUInteger objectID;
then you'd implement isEqual: as
- (BOOL)isEqual:(id)object
{
return (self.objectID == [object objectID]);
}
and you could implement hash:
- (NSUInteger)hash
{
return self.objectID; // to be honest, I just do what Apple tells me to here
// because I've forgotten how Sets are implemented under the hood
}
Then, you can get an object with that ID (as well as check for whether it's in the NSSet) with:
MyObject *testObject = [[MyObject alloc] init];
testObject.objectID = 5; // for example.
// I presume your object has more properties which you don't need to set here
// because it's objectID that defines uniqueness (see isEqual: above)
MyObject *existingObject = [mySet member: testObject];
// now you've either got it or existingObject is nil
But yeah, the only way to get something out of a NSSet is by considering that which defines its uniqueness in the first place.
I haven't tested what's faster, but I avoid using enumeration because that might be linear whereas using the member: method would be much faster. That's one of the reasons to prefer the use of NSSet instead of NSArray.
for (id currentElement in mySet)
{
// ** some actions with currentElement
}
Most of the time you don't care about getting one particular object from a set. You care about testing to see if a set contains an object. That's what sets are good for. When you want to see if an object is in a collection sets are much faster than arrays.
If you don't care about which object you get, use -anyObject which just gives you one object from the set, like putting your hand in a bag and grabbing something.
Dog *aDog = [dogs anyObject]; // dogs is an NSSet of Dog objects
If you care about what object you get, use -member which gives you back the object, or nil if it's not in the set. You need to already have the object before you call it.
Dog *spot = [Dog dogWithName:#"Spot"];
// ...
Dog *aDog = [dogs member:spot]; // Returns the same object as above
Here's some code you can run in Xcode to understand more
NSString *one = #"One";
NSString *two = #"Two";
NSString *three = #"Three";
NSSet *set = [NSSet setWithObjects:one, two, three, nil];
// Can't use Objective-C literals to create a set.
// Incompatible pointer types initializing 'NSSet *' with an expression of type 'NSArray *'
// NSSet *set = #[one, two, three];
NSLog(#"Set: %#", set);
// Prints looking just like an array but is actually not in any order
//Set: {(
// One,
// Two,
// Three
// )}
// Get a random object
NSString *random = [set anyObject];
NSLog(#"Random: %#", random); // Random: One
// Iterate through objects. Again, although it prints in order, the order is a lie
for (NSString *aString in set) {
NSLog(#"A String: %#", aString);
}
// Get an array from the set
NSArray *array = [set allObjects];
NSLog(#"Array: %#", array);
// Check for an object
if ([set containsObject:two]) {
NSLog(#"Set contains two");
}
// Check whether a set contains an object and return that object if it does (nil if not)
NSString *aTwo = [set member:two];
if (aTwo) {
NSLog(#"Set contains: %#", aTwo);
}