I am pulling data from the web that is formatted in JSON and when I parse the data using "ValueForKeyPath" it stores the string value as an id object.
So I store the data into a NSMutableArray
In the debugger window it shows all the elements added as (id) null.
I have an if statement
if ([[self.activeCategories objectAtIndex:selected] boolValue] == true)
Sometimes I would say 20% of the time it fails the if statement when it should not.
I was wondering if it was because the self.activeCategories is storing id types. Do I need to do [NSString stringWithFormat#"%#", x] to all the objects inside the array? It seems like when I just straight cast it by using (NSString *) it is still type id in the debugger.
It's a very strange error to me... as the error is not consistently reproducible.
Try it like that:
if ([[self.activeCategories objectAtIndex:selected] boolValue])
According to that article a BOOL may hold values other than 0 and 1 which may fail the comparison.
Related
I have two objects:
deviceConfigInfo and deviceStatusInfo
Both contain an array of devices (so theres a third device object actually).
For each device returned in deviceConfigInfo there are these properties:
uuid
name
somethingElse
lookAnotherOne
and for deviceStatusInfo
uuid
name
somethingElse
someStatusInfo
someMoreStuff
(If you hadn't guessed, I just made up some random properties)
So back to that third object I mentioned, device, I created it with all the properties combined. Now, my question is, say the deviceStatusInfo gets updated, how can I update the device object without losing the "old" data that isn't overwritten (in this case, the lookAnotherOne property).
Does it have to be a manual process of getting the device with the matching uuid and then updating each of the properties for deviceStatusInfo or is there a quicker way of doing this? Imagine there were loads of properties.
Hopefully this makes sense. If it helps, I am using Mantle to create the objects/models.
I noticed that Mantle has the following function which I was able to use:
mergeValueForKey:fromModel:
So in my device model, I added two functions:
mergeConfigInfoKeysFromModel:
mergeStatusInfoKeysFromModel:
These functions have access to an array that contains NSString values representing the properties/keys. There is one array for the configInfo and another for statusInfo properties/keys.
I then loop through the keys and use valueForKey to check it has an actual value. If it does, I then call the mergeValueForKey:fromModel:.
Example Code:
- (void)mergeConfigInfoKeysFromModel:(MTLModel *)model
{
NSArray *configInfoKeys = #[#"uuid", #"name", #"somethingElse", #"lookAnotherOne"];
for (NSString *key in configInfoKeys) {
if ([model valueForKey:key]) {
[self mergeValueForKey:key fromModel:model];
}
}
}
All I have to do now, is call the appropriate merge function on the device object when I get an update, passing over the updated device object. Just as below:
[self.device mergeConfigInfoKeysFromModel:deviceUpdate];
I have an NSMutableDictionary that loads data from a certain source.
He is loading fine and while debugging i am sure he is loading and that in the end he has the exact number of elements. I also can see the elements as they should while debugging.
I just want to get the value of the the row having the key value = 3 .
I tried NSString * myString = [myMutabDict objectForKey:#"3"], considering that the value is in string ,and i followed it with the debugger, and i am sure that my NSDictionary has elements in it , and i can see them while debugging, and i can see the key 3 , but i still get null as an output …
What am i missing?
If you are sure you see a value with a key of 3 and you can't load it using the key #"3" then it is possible that the key is a number. Use:
NSString *myString = [myMutableDict objectForKey:#3];
or modern syntax:
NSString *myString = myMutableDict[#3];
I am trying to save a NSDictionary to NSUserDefaults, and am using MD5 hash to check for integrity, using this helpder class: Secure-NSUserDefaults.
The code to set the Dictionary:
#import "NSUserDefaults+MPSecureUserDefaults.h"
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setSecureObject:aDictionary forKey:aKey];
[defaults synchronize];
The code to retrieve it:
BOOL valid = NO;
NSDictionary * aDictionary = [defaults secureDictionaryForKey:aKey valid:&valid];
if (!valid) {
//... hash doesn't match
} else {
//... hash matches
}
This works great as long as the app is running (testing in the simulator right now), but when I exit the simulator and restart the app, the hash value is different than before.
It's as if exiting the app changes the dictionary value (when it's saved to disk perhaps?) in some way. It's not adding visible characters, though, because it looks exactly the same in the debugger.
Would appreciate any ideas from more experienced programmers!
EDIT:
So this seems to work for me. Thoughts?
Change NSUserDefaults+MPSecureUserDefaults.m like so:
- (NSString *)_hashObject:(id)object
{
if (_secretData == nil) {
// Use if statement in case asserts are disabled
NSAssert(NO, #"Provide a secret before using any secure writing or reading methods!");
return nil;
}
// Copy object to make sure it is immutable (thanks Stephen)
object = [object copy];
//added check for array or dictionary
if ([NSJSONSerialization isValidJSONObject:object]) {
NSMutableData *archivedData = [[NSJSONSerialization dataWithJSONObject:object options:0 error:nil] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
return hash;
}
// Archive & hash
NSMutableData *archivedData = [[NSKeyedArchiver archivedDataWithRootObject:object] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
////[archivedData release];
return hash;
}
The code you are using, Secure-NSUserDefaults, is incorrect.
The code makes assumption about NSKeyedArchiver's archivedDataWithRootObject: which are invalid - namely that if two dictionaries are the same then the archived version of them is the same. The internal ordering of key/value pairs in a dictionary is not defined, two dictionaries can be semantically the same while being structurally different - and if they are structurally different their archived version of them may be also.
Either write your own or fix the library you have used. You need to deal with dictionaries as an ordered collection of key/values pairs - say by sorting based on the key as NSLog does when printing them.
HTH
Addendum: After question edit
NSJSONSerialization suffers from the same problem (for this usage) as NSKeyedArchiver, as the simple test I posted on GitHub will show.
It seems you may be missing the core problem here. A dictionary is an unordered collection of key/value pairs. The code you are using is attempting to generate a sequence of bytes which is identical (or at least produces the same hash value) for different dictionaries which contain the same key/value pairs in any order. The issue is compounded as dictionaries/arrays can contain other arrays/dictionaries to any nesting depth.
The obvious way to do generate a byte sequence independent of the (internal) ordering is to order the key/values pairs when producing the byte sequence. However dictionary keys are not required to have an ordering, only an equality, relation.
As there is no ordering requirement on keys the NSKeyedArchiver and NSJSONSerialization cannot assume one exists and so do not guarantee to produce the same byte sequence for dictionaries with the same key/value pairs which are ordered (internally to the type) differently. Furthermore NSKeyedArchiver is preserving the object graph, including any sharing, see Object Graphs, which could also contribute to the differences you observe.
However you are writing property lists and for a dictionary to be valid for inclusion in a property list the keys must be strings (see Apple's About Property Lists). Now strings do have an ordering, e.g. NSString's compare: method, so in this particular case you can order the key/value pairs. So you can either write your own code, or find pre-written code, which produces a byte stream for property list types and orders the dictionary key/value pairs while doing so; then you can use this code in the library you are trying to adopt.
Just an idea how this class may be fixed:
NSDictionary should be archived with NSKeyedArchiver not only to calculate hash over it, but also to be saved like that (archived) in the NSUserDefaults (in opposite to the direct storing as it is done now).
In the get method, upon the hash validation, it will be needed additionally to unarchive it with NSKeyedUnarchiver to get back original value.
Thanks.
I have been attempting to debug a issue with my code, and just came upon an odd phenomenon. Found this in the debug area when a breakpoint was triggered:
Am I correct in observing that there are multiple values for this key: #"6898173"??
What are possible causes of this? I do not set those key-value pairs using the string literal, but by getting a substring of a string retrieved and decoded from a GKSession transmission.
I still have this up in the debug area in xcode, incase theres anything else there that might help.
EDIT:
By request, here is the code that would have created one of the two strings (another was created at an earlier time):
[carForPeerID setObject:[[MultiScreenRacerCarView alloc] initWithImage:[UIImage imageNamed:#"simple-travel-car-top_view"] trackNumber:[[[NSString stringWithUTF8String:data.bytes] substringWithRange:range] intValue]] forKey:[[NSString stringWithUTF8String:[data bytes]] substringFromIndex:9]];
The string in data might look something like this:
car00.0146898173
EDIT:
Code that sends the data:
[self.currentSession sendData:[[NSString stringWithFormat:#"car%i%#%#", [(MultiScreenRacerCarView *)[carForPeerID objectForKey:peerID] trackNumber], speed, [(MultiScreenRacerCarView *)[carForPeerID objectForKey:peerID] owner]] dataUsingEncoding:NSUTF8StringEncoding] toPeers:#[(NSString *)[peersInOrder objectAtIndex:(self.myOrderNumber + 1)]] withDataMode:GKSendDataReliable error:nil];
Sorry its hard to read. Its only one line.
What you're seeing is a debugger "feechure". When you have a mutable dictionary and modify it, the debugger may not show you the correct view of the object.
To reliably display the contents of an NSMutableArray or NSMutableDictionary, switch to the console and type po carForPeerID.
I'm building a project and want to add a NSNumber into a NSDictionary. But the it crashed because of the null value. So, I created another small program to check what happened. As you can see in the snapshot: Why the the value of NSNumber in NSDictionary is null?
I've run your code and I could reproduce the problem. But it seems like a debugger problem. For instance, if after your dictionary is created, go to the console and try printing the dictionary.
po dictionary
My result is like:
$4 = 0x2083e770 {
number = 1;
}
So it's not null at all. Also, after that, anum is assigned correctly and b is set to YES. So it really looks like a debugger issue instead of a bug from you.