Obj-C setValuesForKeysWithDictionary 64-bit vs 32-bit - ios

I am pulling a JSON object from my API and creating it using the following code (in hindsight not the way to go):
+ (YActivity *)instanceFromDictionary:(NSDictionary *)jsonDictionary
{
YActivity * instance = [[YActivity alloc] init];
[instance setAttributesFromDictionary:jsonDictionary];
return instance;
}
- (void)setAttributesFromDictionary:(NSDictionary *)jsonDictionary
{
if (![jsonDictionary isKindOfClass:[NSDictionary class]]) {
return;
}
[self setValuesForKeysWithDictionary:jsonDictionary];
}
One of the keys is "type". I have a read-only variable #synthesized called "type". On the 32-bit version of my app, this is set right away before setValue:(id)value forUndefinedKey:(NSString *)key is called. I reference this value in that method, and on the 64-bit version of my app, when the breakpoint hits this method, type is not set yet.
Clearly this isn't the best course of action. I am just wondering if anyone else as seen this or if I'm barking up the wrong tree. I diffed the files between the two versions and they are identical. I am running them both on iOS 8.1 Simulator, the API is returning the same thing for both...I'm stumped. Basically on the old version defined keys are set before undefined, and on the new version it seems the opposite of that.

NSDictionary objects are unordered collections, so code should never make assumptions about the order in which a dictionary will enumerate its own keys. It turns out that there are implementation differences between the 32- and 64-bit runtimes that affect where hashed values end up being stored.
Since the API contract explicitly doesn't guarantee order, that shouldn't cause problems, but it can (and in this case apparently does) have the side-effect of causing code that formerly 'worked' to break when compiled for the 64-bit architecture.
A quick way to fix the problem you're currently having without significantly changing the implementation would be to enumerate the dictionary's keys yourself, which would allow you to provide an array of keys ordered however you wish:
- (void)setAttributesFromDictionary:(NSDictionary *)dictionary
{
// So instead of doing this...
//
// [self setValuesForKeysWithDictionary:dictionary];
// You could do something along these lines:
//
NSMutableArray *keys = dictionary.allKeys.mutableCopy;
// TODO: insert code to change the order of the keys array.
// Then loop through the keys yourself...
for (NSString *key in keys)
{
[self setValue:dictionary[key] forKey:key];
}
}

Related

Merge two objects of same type

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];

Looking for code to 'set current key' when saving data --iOS App--

I recently posted a question regarding some issues I'm having saving data into text fields for an app I'm building in Xcode. The question can be seen here,
Having issues saving data from multiple text fields in Xcode
I received a very helpful answer which helped clue me in to exactly where I was going wrong, but I'm still unsure about the code needed to set the current key when specifying which field to save data from.
Here is the code:
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (![self.tField.text isEqualToString:#""]) {
//SET THE CURRENT KEY HERE
[Data setNoteForCurrentKey:self.tField.text];
}
else {
//SET THE CURRENT KEY HERE
[Data removeNoteForKey:[Data getCurrentKey]];
}
if (![self.tField2.text isEqualToString:#""]) {
//SET THE CURRENT KEY HERE
[Data setNoteForCurrentKey:self.tField2.text];
}
else {
//SET THE CURRENT KEY HERE
[Data removeNoteForKey:[Data getCurrentKey]];
}
[Data saveNotes];
}
I added a second detail item property to enable the second text field to be saved, but don't know what code to use to call upon the different detail items. Any help will be greatly appreciated, thanks!
setNoteForCurrentKey: should take two parameters, not one. Then you can pass the key and the value associated with it. Something like:
- (void)setNote:(NSString *)note forKey:(NSString *)key
Using a 'current key' just makes maintenance of that information an issue that you don't need.
In your current code, because you have 2 if statements, you can set the keys as literal values in the code. That isn't very scalable, so going forwards you might want to think about storing your text fields in an array and having another array with the corresponding key names.

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.

Convert NSString into executable statement

I am facing an issue with converting NSString into executable statement of objective C.
For example,
NSString *strColorAssembly = #"[UIColor redColor]";
Now, I need to convert this string into an executable code and pass to .color property.
This is just an example, I am trying to build a project in which everything will be dynamic, so It would be much helpful if anyone can provide me right direction to go ahead.
I'm not sure if you can programmatically accomplish that goal. If it were me I would implement a method that accepted an NSString (or an NSInteger) and return a UIColor.
- (UIColor *)colorDecode:(NSString *)colorPassed
{
if ([colorPassed isEqualToString #"red"])
{
return [UIColor redColor];
}
else if
.
.
.
}
OR
- (UIColor *)colorDecodewithInt:(NSUInteger)colorIndex
{
switch (colorIndex)
{
case (0)
{
return [UIColor redColor];
break;
}
case (1)
.
.
.
.
default
{
return <some default color>;
break;
}
}
}
EDIT:
I suppose you could also use an NSDictionary that consists of a collection of UIColor objects as well. The bottom line is that you're going to be restricted to a pre-defined set of colors in your compiled code unless you start playing with CGColor at which point you can start dynamically pass the RGB values.
It sounds like what you are looking for is the setValue:ForKey: method. Anything that conforms to the NSKeyValueCoding Protocol will have that option available. However, this will only work for the key part (i.e. the name of the property). iOS explicitly prohibits any modification to the distributed code. I would recommend using the keys as strings and writing some kind of interpreter for the rest of your data.
You can't do that on iOS, period. iOS prevents you from allocating memory pages that can both be written to and also executed.
The best you could manage is an interpreter that takes strings in, parses them, and attempts to map them to Objective-C calls (using NSClassFromString and NSSelectorFromString).
I would recommend not doing what you're trying to do, and instead use one of the many bindings from Cocoa to a dynamic language. I think the most popular ones bind Cocoa to Lua, so then you can write your Lua code and everything will be dynamic.

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