How to store the NSArray with customized Object? - objective-c

I have an NSMutableArray, if this is store string, I can read, and write it successfully, using this method.
[array writeToFile:m_sApplicationPlistPath atomically:YES];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:m_sApplicationPlistPath];
but if my Array add something which is not a simple string, for example, I add a special Object to the array, like this:
[array addObject:[[SpecialObject alloc] init]];
I find that I can't read back the special object, how can I solve it, thank you.

Implement the NSCoding protocol. (encodeWithCoder and initWithCoder)
If you still cannot use the method writeToFile:atomically: then you have to serialize the NSArray to get data, and then write it to an file.
NSData *data = NSKeyedArchiver archivedDataWithRootObject:theArray];

I think you can't do that because [NSArray writeToFile:atomically:] method makes use of property lists, which are only available for certain data types (NSString, NSData, NSArray or NSDictionary).
In order for you to write the array to a file you can make SpecialObject comply to NSCoding protocol and then save the array using -[NSKeyedArchiver archiveRootObject:toFile:] using the array as root object.

You can get this to work by using methods defined in the NSKeyValueCoding protocol to convert the instances of your custom class to instances of NSDictionary before you write them. You could do a similar conversion after reading the plist file back in to recreate the objects.
Here's an example that converts an array of instances of a custom Book class to an array of dictionaries, using the dictionaryWithValuesForKeys: method declared in NSKeyValueCoding (and implemented by NSObject, so all objects inherit this behavior):
+ (NSArray *)dictionariesFromBooks:(NSArray *)books
{
NSMutableArray *bookDicts = [NSMutableArray arrayWithCapacity:[books count]];
for (Book *currBook in books)
{
NSDictionary *currDict = [currBook dictionaryWithValuesForKeys:[Book keys]];
[bookDicts addObject:currDict];
}
return bookDicts;
}
Similarly, here's a method that populates instances of Book using the NSKeyValueCoding method setValuesForKeysWithDictionary:
+ (NSArray *)booksFromDictionaries:(NSArray *)bookDicts
{
NSMutableArray *books = [NSMutableArray arrayWithCapacity:[bookDicts count]];
for (NSDictionary *currDict in bookDicts)
{
Book *currBook = [[Book alloc] init];
[currBook setValuesForKeysWithDictionary:currDict];
[books addObject:currBook];
[currBook release];
}
return books;
}
With a little work, this can be made to cascade to nested custom objects. For example, if Book contained a nested Author instance, you could override NSKeyValueCoding methods such as setValue:forKey: and dictionaryWithValuesForKeys: to convert the instance on the fly.

From docs:
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.

Related

Convert NSDictionary object to Object

I'm receiving a JSON payload of data from a MVC API in my iOS application. NSJSONSerialization then serializes this into an object. This object contains some properties and also a list of data objects. The data objects are of type NSDictionary. I already have the class structure of these objects in ObjC (Im using odata so i want to convert the objects to their OdataObject equivalent).
So I'd like to know how I can cast/convert these NSDictionary objects to their corresponding OdataObject class (or any object really)?
You can't cast an NSDictionary instance to be an OdataObject, you either need to explicitly convert the instance or create the appropriate instance when you deserialise the JSON.
You could look at using setValuesForKeysWithDictionary: to push your dictionary contents into another instance using KVC. Whether this will work in this case depends on the OdataObject definition (from github? Not convinced) and the dictionary contents...
Write a class category for NSDictionary that allows the conversion to the OdataObject class? I'm sorry, I don't completely understand what you're asking but if you need to be able to convert NSDictionary to a custom object, then I recommend Class Categories:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Yes, you can not cast your NSDictionary instance to your custom model object. For that you need to write code of conversion.
1) Create a class which inherits NSObject with required properties.
2) Synthesize all the properties
3) Write one private keyMapping method which returns the dictionary with keys you want in your model object as
-(NSDictionary *)keyMapping {
return [[NSDictionary alloc] initWithObjectsAndKeys:
#"key1", #"key1",
#"key2", #"key2",
#"key3", #"key3",
#"key4", #"key4",
#"key5", #"key5",
nil];
}
4) Write class method which takes NSDictionary instance, as a parameter and returns instance of the same model class with filled values from NSDictionary as (Pass your dictionary to this method)
+(ModelClass *)getModelClassObjectFromDictionary:(NSDictionary *)dictionary {
ModelClass *obj = [[ModelClass alloc] init];
NSDictionary *mapping = [obj jsonMapping];
for (NSString *attribute in [mapping allKeys]){
NSString *classProperty = [mapping objectForKey:attribute];
NSString *attributeValue = [dictionary objectForKey:attribute];
if (attributeValue!=nil&&!([attributeValue isKindOfClass:[NSNull class]])) {
[obj setValue:attributeValue forKeyPath:classProperty];
}
}
return obj;
}
Thats it. Hope this helps you.

How to check assignment since addObject doesn't access setter?

I just noticed that calling addObject: on an NSMutableArray doesn't access that array's setter.
E.g., for NSMutableArray self.myArray, [self.myArray addObject:object] does not use [self setMyArray:array] to add the object.
Previously I have been using custom setters and getter to check assignment before assigning; e.g., if I wanted an array that only accepted objects of class MyClass, I would do the following:
- (void)setMyArray:(NSMutableArray *)myArray
{
for (id object in myArray)
{
if (![object isKindOfClass:[MyClass class]]) return;
}
_myArray = myArray;
}
- (NSMutableArray *)myArray
{
if (!_myArray) _myArray = [[NSMutableArray alloc] init];
_myArray = myArray;
}
How do I go about achieving this same functionality when changing the array via addObject:, removeObject:, and other similar functions that may circumvent the setter?
Generally this kind of problem is the reason why NSMutableArray is usually avoided in preference of NSArray.
This is the simple solution, use NSArray instead of NSMutableArray:
self.myArray = [self.myArray arrayByAddingObject:foo];
However, if the array is really big that will cause performance issues. Then you've got two options:
you can have your own addObjectToMyArray: method in your class and always use that
you can create an NSArrayController and use that to access your array. It will implement key value observing and bindings and all of that stuff.
NSMutableArray is designed to perform addObject: with as few CPU instructions as possible and therefore does not proved any way for external code to be notified that the object was added. You have to have some other class wrapped around it.
Do not try to subclass NSMutableArray, because it is a "class cluster" making subclasses extremely complicated.
If what you wish to do is ensure objects in the array are of a particular class then this answer to the question "NSMutableArray - force the array to hold specific object type only" provides code to do exactly that.
If you wish to do other checks on assignment then you can use the code in that answer as a starting point.

NSMutableArray with a specific class of object to be contained

Is there any way to specify what specific type of object can be contained within an NSMutableArray?
EDIT More specifically... Is there a way to restrict the class that the object must belong to?
Well, you could always subclass NSMutableArray but as everyone else has said, it is hard to imagine a good reason to do this....
From Subclassing Notes in the docs you would basically have to over-ride the following functions and check for the proper class:
insertObject:atIndex:
addObject:
replaceObjectAtIndex:withObject:
the primitive methods of the NSArray class
You could check the class of the object before adding it to the array.
NSMutableArray *myArray = [NSMutableArray alloc] init];
if ([someObject isKindOfClass:[ClassYouWantInArray class]]){
[myArray addObject:someObject];
}

How do I convert a JSON structure to an object in Objective-C?

I have a JSON structure like this:
{
id:100
contactInfo: {
name: John Doe
city: New York
}
}
and a corresponding Customer class w/ the following properties including a nested contactInfo class.
Customer.id
Customer.contactInfo.name
Customer.contactInfo.city
Is it possible to convert the JSON data directly to an instance of the Customer class?
I know how to get an NSDictionary object, but that is very cumbersome and verbose to work with and would rather convert/deserialize the data directly to the Customer class.
EDITED w/ Additional Info:
In other languages I have worked with there is built-in support for deserialization of JSON to custom objects. All you have to do is mark the class as "Serializable" and the JSON is deserialized w/o the need to write custom code in each class.
The NSDictionary object can become very cumbersome if the class has nested classes. It would be much easier to use the custom object properties like this
Customer.contactInfo.name
rather than
[(NSDictionary*)[customerDict objectForKey:#"contactInfo"] objectForKey:#"name"]
thus my question about a standard built-in deserializer in Objective-C.
As of iOS5 there if official support for JSON
https://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html
I don't know why you think it is cumbersome to work with. A simple way to do it would be to have an initialiser for your customer object that took a dictionary as a parameter.
That way you could get your downloaded data as JSON, deserialise it into a dictionary and then pass it to the initializer to create an object from the dictionary.
Any of the dozens of JSON libraries will parse your JSON string and turn it into an NSDictionary. You're going to have to deal with an intermediate format if you don't want to write a parser yourself. But, once you have the NSDictionary you can use Key Value Coding (KVC) to set the properties on your object:
for (NSString *key in jsonDictionary) {
[customer setValue:[jsonDictionary objectForKey] forKey:key];
}
KVC is built into Cocoa, and defines setValue:forKey: for all objects, so you can set properties by name regardless of how they are defined.
You'll have to do something smarter than the loop above to handle your internal ContactInfo class, but you should be able to figure that out. For safety (since the JSON is probably coming over the network) you might want to make a whitelist of keys and loop over that, and use valueForKeyPath:/setValue:forKeyPath: to handle nested objects.
Consider using RestKit api (http://restkit.org/) and its object mapping system...
Here is some wiki : https://github.com/RestKit/RestKit/wiki/Object-mapping
Write an initializer for your Customer class that takes JSON data as a parameter, something like:
-(id)initWithJSONData:(NSData*)data;
You can implement that method to do what Abizern suggests: read the data into a dictionary, and then pull whatever values you need out of the dictionary.
If you want to get fancy, another way you could go is to adopt NSCoding in your Customer class, and then create a NSCoder subclass that deserializes JSON data. This could work very much like NSKeyedUnarchiver. Doesn't seem worth the trouble unless you're going to take the same approach with a number of your classes, though.
Using this library, you can do the following:
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
You can then access your properties using:
[customer objectForKey:#"id"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"name"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"city"];
You can then use this code in your Customer class' init function. For example:
- (id)initWithJSON:(NSString *)jsonString {
self = [super init]; // or whatever you need here
if (self) {
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
self.id = [customer objectForKey:#"id"];
self.contactInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[[customer objectforKey:#"contactInfo"] objectForKey:#"name"], #"name", [[customer objectforKey:#"contactInfo"] objectForKey:#"city"], #"city"]
}
return self;
}

Is there any way to specify the class of objects of a NSMutableArray?

Im having the following problem:
I've made a NSMutableArray "array" that is going to contain objects of a class named "Class". At the start that array should be empty and it must be filled during the program's execution.
As I never actually told the compiler that my NSMutableArray will be holding elements of the class Class, when I try to write the appropriate methods the compiler wont let me do it.
This is my first experience on Objective-C and iPhone development. I used to code in C/C++ where I declared my arrays in the following way:
Class array[NUMBEROFELEMENTS];
Is there any way to do this in Objective-C?
Thanks!
The truth is that is doesn't matter to the NSMutableArray what type of object it is. NSMutableArray simply stores pointers to all the objects they contain, or reference.
The trick is when you pull the object back out of the array you need to create a new pointer based on the appropriate type:
MyObject *myObject = [myArray objectAtIndex:0];
Then you can use the object however you like:
[myObject doThatThingWithThisValue:10];
Or whatever you need.
Arrays in Objective-C Cocoa are objects (as well as other collections, sets, dictionaries). Arrays can contain references to objects of any type, so the type for the array is simply NSArray, NSMutableArray, etc...
Since they are objects, you can send them messages to manipulate their content.
I suggest you take a look at Apple's excellent Collections Programming Topics, which explain the rudiments of collections.
Here is a quick example :
// two objects of different types
NSNumber *n = [NSNumber numberWithInteger:10];
NSString *s = #"foo";
// alloc/init a new mutable array
NSMutableArray *a = [NSMutableArray arrayWithCapacity:10];
// add an object
[a addObject:n];
[a addObject:s];
// array a now contains a NSNumber and a NSString
Well, you can still have C-style arrays in Objective-C.
However, the characteristics of Objective-C (some people will call it strength, other will call it weakness) is that it has dynamic typing of objects and dynamic dispatch.
It has NSArray and NSMutableArray which are not specialized for the certain class. It can store objects of non-compatible classes.
You can use the following idiom: [obj isMemberOfClass: [Class type]] to make sure an array element is of the desired type and then cast to Class*.
You can also use for-each loop (aka Fast Enumeration):
NSMutableArray* array = //... initialize your array
for (Class* elm in array) {
elm.your_property = 10;
}