nested json with restkit on iphone - iphone

just trying to get used to restkit for iphone apps here at the moment and ive hit a wall. Im trying to get data from a nested json file.
[
{"person": {
"name": "joe",
"id": "1234",
"numbers":
[
{"value": "555-12125"},
{"value": "222-12125"}
]
}
}]
i set up the rkobjectmanager like so
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:3000"];
[manager loadObjectsAtResourcePath:#"/J.json?user_id=36995582&planner=insta-grammar" objectClass:[Person class] delegate:self] ;
[manager registerClass:[Person class] forElementNamed:#"person"];
[manager registerClass:[Numbers class] forElementNamed:#"numbers"];
then the person class and numbers class as follows
#import "Person.h"
#implementation Person
#synthesize _name,_id,_number;
+ (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"name",#"name",
#"id",#"id",nil];
}
+ (NSDictionary*)elementToRelationshipMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"numbers", #"numbers",
nil];
}
#end
#import "Numbers.h"
#implementation Numbers
#synthesize _number;
+ (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"value",#"value",nil];
}
+ (NSString*)primaryKeyProperty {
return #"value";
}
+ (NSDictionary*)relationshipToPrimaryKeyPropertyMappings {
return [NSDictionary dictionaryWithObject:#"value" forKey:#"value"];
}
#end
but every time the _number remains empty after the didloadobjects function is called while the name and id variables fill up fine.
any ideas on this?.
ive tried the example on git but couldnt get it to work so any help would be appreciated.
thanks
g

What you need to do is to declare an NSArray *_numbers member variable in Person.h, make it a property like #property (nonatomic, retain) NSArray *_numbers and synthesize it in Person.m like #synthesize numbers = _numbers. Then you can get rid of whatever _number is, since you won't be storing a single number object, you will be storing an array of number objects.
Your Numbers class should instead just be Number, and you should synthesize your number variable like #synthesize number = _number;. So your Number.m should look like
#implementation Number
#synthesize number = _number;
+ (NSDictionary *)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"value", #"number", nil];
}
Because your property is number, the mapping dictionary value is #"number", while your source JSON key should be #"value".
Then you can load the objects like (note we are using our Number class for the #"numbers" element):
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:3000"];
[manager registerClass:[Person class] forElementNamed:#"person"];
[manager registerClass:[Number class] forElementNamed:#"numbers"];
[manager loadObjectsAtResourcePath:#"/J.json?user_id=36995582&planner=insta-grammar" delegate:self];
For a full example see the Catalog Example int the RestKit github project

Just for anyone following up on this. Finally got this all working using the new OM2 that they have just released with restkit. very nice and easy to use change

Related

Access NSDictionary via dot notation?

Is there a way via dot notation to access the values of keys in an NSDictionary like this?
NSDictionary *returnVal = [NSDictionary dictionaryWithObjectsAndKeys:#"Saturn", #"name", #"Gas Giant", #"type", nil];
NSLog(#"VALUE: %#", [returnVal valueForKey:#"name"]); // This is how I am doing it now.
There is no dot syntax for NSDictionary, but should consider using objectForKey: instead of valueForKey:
Difference between objectForKey and valueForKey?
Not really, no.
The dot notation is a shorthand way of calling a method with that selector name. In other words, this...
NSLog(#"Hello, %#", foo.bar.name);
...is the same as this...
NSLog(#"Hello, %#", [[foo bar] name]);
When I say "same", I mean they are compiled down to the same code. It's just syntactic sugar.
A plain NSDictionary won't act that way. You could sort of fake it with Key Value Coding, which lets you call valueForKeyPath to get properties like this:
NSLog(#"Hello, %#", [foo valueForKeyPath:#"bar.name"]);
If you really wanted to be able to write foo.bar.name in your code, however, you'd have to make a custom class that overrides forwardInvocation:; this lets you catch an unknown message to an object and do something else with it besides throw an error. In this case, you could change the unknown selector to a lookup on an NSDictionary instance it contains.
But even if you did that, the compiler would probably still generate warnings unless you made header files that declared those property names to exist.
I agree with most of the answers that NSDictionary should be accessed with objectForKey: or similar methods. However it is possible to allow for dot notation access to a NSDictionary, and for learning purposes this might be interesting for someone. Also when for example your are retrieving large JSON dictionaries via AFNetworking, this method can ease the access and readability of your code.
This is my solution:
DictionaryProperties.h: (class wrapping the NSDictionary for property access)
#interface DictionaryProperties : NSObject{
NSMutableDictionary* _backingDict;
}
#property (nonatomic, strong) NSMutableDictionary* backingDict;
+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict;
#end
DictionaryProperties.m:
#import "DictionaryProperties.h"
#implementation DictionaryProperties
#synthesize backingDict = _backingDict;
- (id) initWithDictionary:(NSDictionary*)dict {
if (self) {
if ([dict isKindOfClass:[NSMutableDictionary class]]) {
self.backingDict = (id)dict;
} else {
self.backingDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
}
}
return self;
}
+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict {
return [[DictionaryProperties alloc] initWithDictionary:dict];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString* key = NSStringFromSelector(invocation.selector);
invocation.selector = #selector(objectForKey:);
[invocation setArgument:&key atIndex:2];
if ([self.backingDict objectForKey:key]) {
[invocation invokeWithTarget:self.backingDict];
} else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.backingDict methodSignatureForSelector:#selector(objectForKey:)];
}
#end
ExampleDictContent.h: (class declaring what is inside the dictionary)
#import "DictionaryProperties.h"
#interface ExampleDictContent : DictionaryProperties
#property (strong, nonatomic) NSString* someData;
#property (strong, nonatomic) NSString* someOtherData;
#end
#implementation ExampleDictContent
#end
Usage: (simple declaration of a dictionary, allocation of wrapper and property access)
#import "ExampleDictContent.h"
NSDictionary* d = [NSDictionary dictionaryWithObjects:NSArray arrayWithObjects:#"someData content", #"someOtherData content", nil
forKeys:NSArray arrayWithObjects:#"someData", #"someOtherData", nil];
ExampleDictContent* dictWProps = [ExampleDictContent allocWithDictionary:d];
NSLog(dictWProps.someData);
NSLog(dictWProps.someData);
This will print:
someData content
someOtherData content
So basically DictionaryProperties works as a facade for accessing the NSDictionary. It uses forwardInvocation to convert a get-property method call into a getObjectForKey: call on the dictionary. What I like about it, is that it allows for autocompletion on the dictionary, and also allows me to explicitly declare what keys I want to access (in the ExampleDictContent.h file). Note that this solution does not allow for write access to the properties, but that can be added as shown in the link below.
This solution has partly been inspired by karstenlitsche's solution. The main difference is that this solution is based on sub-classing instead of categories.
No, I don't think so.
From the reference manual.
Accessing Keys and Values
– allKeys
– allKeysForObject:
– allValues
– getObjects:andKeys:
– objectForKey:
– objectsForKeys:notFoundMarker:
– valueForKey:
That's listed as the only way to access the keys and the values. So you are doing it alright.
You would be able to access it if the keys were a public property and it was readable.
The way that you have mentioned for accessing element of dictionary is ideal way(using keys).
If you want to do something else, might be you can use-
NSArray *allValues = [returnVal allValues];
Now using this array as well you can perform tasks.
And if you want something specific then mention that, might be for that there can be some other way.
Also as NSDictionary class won't have any property defined, so dot notation is directly not possible.
No, you are doing it the correct way. In the iOS world, often the correct way is the only way. :)
If you really want dot notation (and other nice things you get with typed objects), you're going to have to stuff the dictionary representation into an object. Most commonly my interface will look like:
#interface FooBar : NSObject {
NSString *someData;
int someNumber;
}
#property (nonatomic, copy) NSString *someData;
#property (nonatomic, assign) int someNumber;
+ (FooBar *)FooBarFromDictionary:(NSDictionary *)dataDict;
#end
The implementation should be clear. Then you can
FooBar *fb = [FooBar FooBarFromDictionary:data];
NSLog(#"fb.someData = %#", fb.someData);
Technically, you can do something like this:
typedef id (^valueBlock)(id);
#interface NSDictionary(dotNotationAddons)
#property(nonatomic, readonly) valueBlock value;
#end
#implementation NSDictionary(dotNotationAddons)
-(valueBlock) value
{
return [[^(id key) {
return [self objectForKey:key];
} copy] autorelease];
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"1", #"One", #"2", #"Two", #"3", #"Three", #"4", #"Four", nil];
id value = dictionary.value(#"One");
NSLog(#"%#", value);
}
return 0;
}
I don't know if that is what you were looking for, but I hope it helps!
The answer's still no, but you can use the shorthand
myDictionary[#"key"]
instead of
[myDictionary objectForKey:#"key"]
In Swift, there is a solution that may not seem very elegant but does the trick.
It will require a typeAlias for each specific type of Dictionary and also an extension with variables (with getter/setter) for each of the expected keys in your dictionary. Not a good practice at all
It may be easier wrap your dict object in an object (class/struct) with the same treatment.
typealias MyDict = [String:AnyObject]
extension MyDict {
var key: AnyObject? {
get { return self["key"] }
set { self["key"] = newValue }
}
}
// Usage
var myDict = MyDict()
// Get the value
myDict["key"] = "value1" as AnyObject
if let str = myDict.key {
print(str) // prints "value1"
}
// Set the value
myDict.key = "value2" as AnyObject
if let str = myDict["key"] {
print(str) // prints "value2"
}

plist to array "Collection was mutated while being enumerated" exception [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Getting exception as “Collection was mutated while being enumerated”
This question is a continuation of another question I posted earlier How to read in plist data into a data model?
#devdavid's helped me to get this far...
I have a plist file called "HotelList.plist" and it looks like this:
<array>
<dict>
<key>hotelID</key>
<integer>0</integer>
<key>name</key>
<string>Solmar</string>
// ... more keys and strings ...
</dict>
// ... more hotel entries ...
</array>
I have a "hotel" class describing the keys.
I have a data model class where I would like to read in this plist into an array.
#import "DataModel.h"
#import "Hotel.h"
// Private methods
#interface DataModel ()
#property (nonatomic, retain) NSMutableArray *hotels;
-(void)loadHotels;
#end
#implementation DataModel
#synthesize hotels;
- (id)init {
if ((self = [super init])) {
[self loadHotels];
}
return self;
}
- (void)dealloc {
[hotels release];
[super dealloc];
}
- (void)loadHotels {
NSBundle* bundle = [NSBundle mainBundle];
NSString* plistpath = [bundle pathForResource:#"HotelList" ofType:#"plist"];
hotels = [[NSMutableArray arrayWithContentsOfFile:plistpath]retain];
for (NSDictionary *hotelDict in hotels) {
Hotel *hotel = [[Hotel alloc] init];
hotel.hotelID = [[hotelDict objectForKey:#"hotelID"] intValue];
hotel.name = [hotelDict objectForKey:#"name"];
[hotels addObject:hotel];
[hotel release];
}
}
#end
When I run this, the debugger shows me that each hotel dict was read in but when it reaches the end of the plist (I have about 30 hotels), it tries to go back to the first one and crashes, giving an exception "Collection was mutated while being enumerated".
The green SIGABRT indicator stops on the
for (NSDictionary *hotelDict in hotels) {
line. Is there something wrong with my for loop? The way I set up the arrays/dictionaries? Or maybe the formatting of the plist is wrong (although I don't think so because the debugger shows me it is reading it correctly)?
For completeness, I should mention that, yes, the plist file is present and is in the mainBundle, and spelled correctly. Also, the data in the plist is static -- I won't have to save anything new to a file.
Please help!
Use accessors, not ivars directly. Your problem would have been more obvious that way, and you'd avoid the possible leak you have in the assignment of hotel (if loadHotels is every called a second time). Your code reads dictionaries into an array and then tries to append Hotel objects onto that same array. Here's what you really meant to say:
NSArray *hotelDicts = [[NSArray alloc] initWithContentsOfFile:plistpath];
self.hotels = [NSMutableArray array];
for (NSDictionary *hotelDict in hotelDicts) {
Hotel *hotel = [[Hotel alloc] init];
hotel.hotelID = [[hotelDict objectForKey:#"hotelID"] intValue];
hotel.name = [hotelDict objectForKey:#"name"];
[self.hotels addObject:hotel];
[hotel release];
}
[hotelDicts release];
You're attempting to modify the collection you're enumerating through -- you can't do this. Instead of using an enumerator, try iterating through the collection instead. When you iterate through an array, you can still add/remove members of the array because you aren't bound to the enumeration that was defined prior to begining the for block.
One way to iterate through the collection is with a simple for loop:
for( int i = 0; i < [array count]; i++ )
{
id object = [array objectAtIndex:i];
// do something with object
}

Sorting 2-dimensional arrays

I have the following case. I import data from an xml feed and from facebook graph api, in this case posts. I want to merge this data in a array and sort this on the included date data.
I have now the following:
[containerArray addObject: [NSMutableArray arrayWithObjects: created_time, message, picture, fbSource, nil ]
];
This creates a 2-dimensional array, but i want to order all the entries on created_time.
How can i best solve this problem? Thnx in advance!!
Create a data class containing the necessary instance variables instead of the mutable array. Then you can use the various sort method of the NSArray class, for example sortedArrayUsingDescriptors.
A sort could look like this:
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"created_time"
ascending:YES] autorelease];
NSArray *sortedArray = [containerArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
EDIT
To quote Mr. Fowler from his book Refactoring: Improving the Design of Existing Code.
Replace Array with Object
You have an array in which certain elements mean different things.
Replace the array with an object that has a field for each element
...
Motivation
Arrays are a common structure for organizing data. However, they should be used only to contain a collection of similar objects in somre order.
That's what we want to do here. Let's create a simple Posts class. You can easily add your custom initializer which accepts the four values as parameters, or even a convenience class method to return an autoreleased object later on. This is just a basic skeleton:
Post.h
#interface Posts : NSObject
{
NSDate *created_time;
NSString *message;
UIImage *picture;
id fbSource; // Don't know what type :)
}
#property (nonatomic, retain) NSDate *created_time;
#property (nonatomic, copy) NSString *message;
#property (nonatomic, retain) UIImage *picture;
#property (nonatomic, retain) id fbSource;
#end
Post.m
#import "Post.h"
#implementation Post
#synthesize created_time, message, picture, fbSource;
#pragma mark -
#pragma mark memory management
- (void)dealloc
{
[created_time release];
[message release];
[picture release];
[fbSource release];
[super dealloc];
}
#pragma mark -
#pragma mark initialization
- (id)init
{
self = [super init];
if (self) {
// do your initialization here
}
return self;
}
EDIT 2
Adding a Post object to your array:
Post *newPost = [[Post alloc] init];
newPost.reated_time = [Date date];
newPost.message = #"a message";
newPost.picture = [UIImage imageNamed:#"mypic.jpg"];
// newPost.fbSource = ???
[containerArray addObject:newPost];
[newPost release];

Adding items to NSMutableArray and saving/loading

I've used this tutorial to create an app with a table view that is populated using an NSMutableArray. Now I'd like to add the functionality to add additional items to the array and save/load them. I've customized the Fruit class to look like this:
#import <UIKit/UIKit.h>
#interface Fruit : NSObject {
NSString *name;
NSString *instructions;
NSString *explination;
NSString *imagePath;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSString *instructions;
#property(nonatomic,copy) NSString *explination;
#property(nonatomic,copy) NSString *imagePath;
- (id)initWithName:(NSString*)n instructions:(NSString *)inst explination:(NSString *)why imagePath:(NSString *)img;
#end
and the Fruit.m file:
#import "Fruit.h"
#implementation Fruit
#synthesize name,instructions,explination,imagePath;
- (id)initWithName: (NSString*)n instructions:(NSString*)inst explination:(NSString *)why imagePath:(NSString *)img {
self.name = n;
self.instructions = inst;
self.explination = why;
self.imagePath = img;
return self;
}
#end
and this works great, I can load two textviews and an imageView, instead of just one textview. But how would I go about saving any new items the user creates, and loading them (if they exist) when the app gets launched again?
to save your array to disk you need a couple of things.
first you need to add some methods to your fruit class so it conforms to the NSCoding protocol.
The first method is - (id)initWithCoder:(NSCoder *)aDecoder. This method will be called when you create a Fruit object from a saved archive.
Second method is - (void)encodeWithCoder:(NSCoder *)aCoder. This method is used to save your fruit in an archive.
Sounds complicated? Actually it isn't. Just a couple lines of easy to understand code.
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.instructions = [aDecoder decodeObjectForKey:#"instructions"];
self.explanation = [aDecoder decodeObjectForKey:#"explanation"];
self.imagePath = [aDecoder decodeObjectForKey:#"imagePath"];
}
return self;
}
Look at first two lines of this init method. You have to call [super init] and do a check if self is not nil in your initWithName:instructions:explination:imagePath: method too. It won't change anything in this special case, but this will definitely change in the next few classes you write. So use it all the time.
I changed this for you. And I changed the spelling error.
- (id)initWithName: (NSString*)n instructions:(NSString*)inst explination:(NSString *)why imagePath:(NSString *)img {
self = [super init];
if (self) {
self.name = n;
self.instructions = inst;
self.explanation = why;
self.imagePath = img;
}
return self;
}
and the method for encoding:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:name forKey:#"name"];
[aCoder encodeObject:instructions forKey:#"instructions"];
[aCoder encodeObject:explanation forKey:#"explanation"];
[aCoder encodeObject:imagePath forKey:#"imagePath"];
}
It's not necessary that the key name matches the variable name. You don't need to do this. But in my opinion it adds some clarity. As long as you decode with the same name you've used for encoding you can use whatever you want.
First part is done. Next you need to load and save your NSMutableArray to a file. But to do this you need the path to the documents directory. So I created a little helper method that goes into your controller.
- (NSString *)applicationDocumentsPath {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
Then we need to load the array from disk.
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:#"some.fruits"];
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!array) {
// if it couldn't be loaded from disk create a new one
array = [NSMutableArray array];
}
then you add as much fruits as you like, and finally, to save your array to disk you need this line.
BOOL result = [NSKeyedArchiver archiveRootObject:array toFile:path];
you can check result if the archive was done without error.
I guess this should get you started. Happy coding.
You would need to persist your array on the disk and load it when the app launches.
See the answer to this question.
Than you should read about archiving : NSArchiver
You would need to implement 2 method for your Fruit class :
EncodeWithEncoder and InitWithEncoder.
than you could archive you fruits array.
Good Luck.

Correct way to instantiate NSDictionary/NSArray in init without extra retains

I have numerous classes that use the various NSDictionary/NSArray collection classes as ivars but often I run into the problem of my collection class getting released before the containing class is released.
This seems to happen mostly with the collections classes and not with another model class (ie classes that I either created separately or other NS* non-collection classes).
Here are the two variations I've done and seen other people do:
#implementation ClassX
// myDictionary declared as a property in the .h file as this:
// #property (nonatomic, retain) NSMutableDictionary *myDictionary;
#synthesize myDictionary;
- (id)int
{
if (self = [super init])
{
// Option 1:
// If I don't instantiate and assign with 'self',
// myDictionary ivar will not be available
// at times in doSomething.
myDictionary = [NSMutableDictionary dictionary];
// Option 2:
// Doing this, however will keep the dictionary around.
// because I have invoked an extra retain on the dictionary
self.myDictionary = [NSMutableDictionary dictionary];
// Which one is more correct?
}
return self;
}
- (void)doSomething
{
// this will give the error about trying to invoke
// a method on an already released instance
[myDictionary objectForKey:#"myKey"];
}
- (void)dealloc
{
// If I did self.myDictionary in 'init', I then
// need to do this:
[myDictionary release];
[super dealloc];
}
#end
So which approach is the more correct way to hold an instance of NSDictionary within a class?
Option 2 is correct; Option 1 is wrong.
But you left out the best option: myDictionary = [[NSMutableDictionary alloc] init].
I recommend using
myDictionary = [[NSMutableDictionary alloc] init];
The memory is only within the scope of the method you're in if you call [NSMutableDictionary dictionary]. Once you leave the method, that memory goes with it which is why you need to alloc/init if you want to retain the values.
That's why you don't have to release if you don't encounter an alloc.
So for instance:
- (void) doSomething {
// Do not need to release this string
NSString *someText = #"Hello world!";
// You need to release this string:
NSString *otherText = [[NSString alloc] initWithString:#"Hello world!"];
[otherText release];
}
Edited: Removed self after #mipadi #st3fan and caught my mistake. Forgot to post the change. Thanks for keeping me accountable.