NSKeyedArchiver changing from objectForKey to intForKey - ios

I have an app which stores data using NSKeyedArchiver and everything was working fine.
In the first version of the app a value X was stored as a string, but now I changed it to be stored and saved as an int. The problem is if someone updates from the old version to the newest the app crashes because intForKey: gets called on a key containing a String.
Is there some way during decoding to check if what is being decoded is an int or an object?

NSKeyedArchiver doesn't actually encode primitive values. Instead, it wraps them in an NSNumber.
Perhaps, have you tried using decodeObjectForKey: instead of decodeIntegerForKey:?
In example,
id xObject = [decoder decodeObjectForKey:#"xObjectKey"];
self.x = [xObject integerValue];
// I believe this should work because xObject will either by an NSNumber or NSString
// Both of which respond to the selector integerValue

Related

iOS: syntax to retrieve value of attribute in array?

I have an array of user objects, one of which is stored in a property _selectedUser. When I log it out, it displays as :
{
uid = 5;
uname = xxxx;
}
However, when I try to access the uid with the following, I get an error:
NSNumber *useridnum = _selectedUser.uid;
Error:
[NSKnownKeysDictionary1 uid]: unrecognized selector sent to instance
What is the proper syntax to retrieve value for uid?
You are trying to access the key as a property on the dictionary, not as a value it contains.
As D.C. suggested in the comments, use this code:
NSNumber *useridnum = _selectedUser[#"uid];
Or:
NSNumber *useridnum = [_selectedUser objectForKey:#"uid"];
You said in the comments that the object is an NSManagedObject. I'm not familiar with Core Data, but it looks like you need to use valueForKey::
NSNumber *useridnum = [_selectedUser valueForKey:#"uid"];
An NSDictionary does not store the keys directly as properties; it stores them in some kind of private data structure. It wouldn't be possible to dynamically store keys as properties without messing with the runtime, and then only a very limited set of strings could be used as keys (instead of basically every possible object), because the keys would have to be C identifiers.
You get this error because when you tried to access the key as a property, the system sent a message to the object saying to call the getter method for the property, but that getter method didn't exist, so the app crashed.

iOS 8.1.1 casting #"1" as YES and as NO on different devices

I have an authentication routine that retrieves an encrypted JSON document from an server API for an validation routine.
Once the json message is decrypted it is parsed into a NSDictionary.
Starting with iOS 8.1.1 (and not before) we have some devices in which the following parses to YES and in others it parses to NO.
BOOL isValid = (BOOL)[resp objectForKey : #"IsValid"];
The value of the IsValid property in the json dictionary is { IsValid: "1" }
Up to now it has been working fine, since iOS 6, but iOS 8.1.1 broke this on some devices.
I need to understand why this happened, and if there is anything on the device that may cause this issue.
Is there any reason for this and a way to fix it on the device? I don't want to have to do a new release for many reasons.
I am surprised that the cast has ever worked as intended: casting an object to BOOL should result in comparison of the pointer to nil, and returning YES for all non-nil values. In other words, the cast would produce YES if a value is present, be it #0 or #1, and NO if the value is missing.
To convert based on the value, use boolValue method of NSNumber instead:
BOOL isValid = [[resp objectForKey : #"IsValid"] boolValue];
BOOL is a typedef for signed char. When you cast an object pointer to BOOL, only the low-order 8 bits are preserved. Those 8 bits could be all zero even if the object pointer is not nil, thus a non-nil object pointer could become a false BOOL. (A nil object pointer can't ever become a true BOOL, though.)
This has nothing to do with the OS. It's a completely arbitrary result (which is not the same thing as "random").
That's one of many reasons why such a cast is a terrible idea.
You need to call boolValue on your NSString object. From the docs:
This property is YES on encountering one of "Y", "y", "T", "t", or a
digit 1-9—the method ignores any trailing characters. This property is
NO if the receiver doesn’t begin with a valid decimal text
representation of a number.
Duplicate of this post
typecast BOOL always returns false in iOS 8.1.1
Do a google search before asking.

Saving NSDictionary in NSUserDefaults - hash fails

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.

How to extract a value from a key/value pair in NSDictionary (iOS)

When debugging in XCode, the debugger is telling me that the NSDictionary object contains 1 key/value pair. When the debug console prints the description of the key/value pair is shows:
Printing description of testdictionary:
{
"Unknown (<1809>)" = <000000ff>;
}
I want to extract both the <1809> and the <000000ff>. I have tried both the valueForKey and objectforKey methods as described elsewhere on this site. But I think I am having difficulty understanding what is the key and what is the value here.
For example, is "Unknown (<1809>)" the key? Or is "<1809>" the key? Or is 1809 the key?
Thanks Tim for the reply.
The NSDictionary comes from the CoreBluetoothFramework the didDiscoverPeripheral: method is called and passes advertising data into an NSDictionary called "advertisementData".
This dictionary contains all sorts of stuff like the advertising channel and device name. However, I am trying to extract just the advertising data from "advertisementData". I used the key provided by corebluetooth "CBAdvertisementDataServiceDataKey" like this:
NSData* information;
information = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
I was declaring "information" as an NSDictionary* object before. But changed it to NSData* after some more reading on Apples documentation. The result is the same. The debugger says that it contains a key/value pair as follows:
"Unknown (<1809>)" = <000000ff>;
Thanks again.
Nik
When you do not know the keys that are present in the dictionary, for example, because the key-value pairs come from an external source, you can use enumerateKeysAndObjectsUsingBlock: method to go through all key-value pairs present in the dictionary:
[testdictionary enumerateKeysAndObjectsUsingBlock::^(id key, id object, BOOL *stop) {
NSLog(#"The key is %#", key);
NSLog(#"The value is %#", object);
}];
I've never seen this before so this is nothing more than an educated guess:
The dictionary may have been casted from CFDictionaryRef, in which case both the key and value are const void * (instead of NSObject). The key might have been some Core Foundation type holding a file descriptor (hence 1809). The value could be a pointer (or an integer casted to a "pointer": (void *)32).
You should try and find out where the dictionary originates from, because it's the only thing that's going to give you any valuable information.
Update: the docs state that the value of CBAdvertisementDataServiceDataKey is a dictionary. The keys are CBUUID objects, representing CBService UUIDs and the values are NSData objects. (1)

If Statement Failing sometimes with id BoolValue Comparison?

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.

Resources