Specific situation detail:
In my current photo app, There are such situation : allow user experience first without log in,
but no have some field(e.g:voted,purchased ,etc) in the JSON responded from backend. If user logged in,
the field is added in JSON .
I'll show off my implement details as following:
PhotoListResponseModel (directly inherit MTLModel)
#interface PhotoListResponseModel : MTLModel <MTLJSONSerializing>
...
#property (nonatomic, copy, readonly) NSNumber *foo;
#property (nonatomic, copy, readonly) NSArray<PhotoModel *> *subPhotos;
#end
#interface PhotoModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, copy, readonly) NSNumber *photoID;
#property (nonatomic, copy, readonly) NSURL *imageUrl;
...
#end
#implementation PhotoListResponseModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"foo": #"foo"
#"subPhotos": #"photos"
};
}
+ (NSValueTransformer *)subPhotosJSONTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:PhotoModel.class];
}
#end
#implementation PhotoModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"photoID": #"id",
#"userID": #"user_id",
};
}
#end
Correspondly, the JSON example as following:
noAuthPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar1",
},
{
"id": 204996257,
"image_url": "https://foo2/bar2"
}
],
…
…
}
AuthedPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar2”,
"voted": false,
"purchased": false
},
{
"id": 204996257,
"image_url": "https://foo2/bar2”,
"voted": false,
"purchased": false
}
],
...
...
}
So, how let new field can compatible my already existing code?
Optional Property? (I not know how to do this.)
Or add a subclass of inherit from PhotoListResponseModel & PhotoModel?
Or any good idea : )
Update: I noticed this information,but still don't know how to do it.
Optional properties are only available in Swift, but similar idea for the Obj-C — objects with possible nil value — should work well. You can add the following NSNumber properties to your PhotoModel:
#property (nonatomic, strong) NSNumber *voted;
#property (nonatomic, strong) NSNumber *purchased;
And assign your BOOL values from parsed JSON during PhotoModel object creation like this:
photoModelObject.voted = [NSNumber numberWithBool:[parsedJson valueForKey:#"voted"]];
photoModelObject.purchased = [NSNumber numberWithBool:[parsedJson valueForKey:#"purchased"]];
Next, when you reference your photo object, just check if these properties have nil value. If they are — your user is not logged in and you don't need to show them. Otherwise, extract BOOL value from them like this:
if (photoModelObject.voted && photoMobelObject.purchased) {
BOOL isVoted = [photoModelObject.voted boolValue];
BOOL isPurchased = [photoModelObject.purchased boolValue];
// Use booleans to present info for registered users
} else {
// There is no info about votes and purchasing provided for the current user
}
Related
Well, I should remember you I am still a beginner in iOS development with objective c. And maybe because of this, the solution is simple but I can´t see it. I have a json who is armed in the following way :
{
"origin_addresses": [
"Test"
],
"rows": [
{
"elements": [
{
"distance": {
"text": "0.3 km",
"value": 339
},
"duration": {
"text": "1 min",
"value": 82
},
"status": "OK"
}
]
}
],
"status": "OK"
}
The app can consume the service without problems. My goal is to reach the distance attribute. Which I do as follows. I use a foreach to get through the array rows and then another foreach to get through the array elements.
for (BPCGARows *item in googleAddress.rows) {
NSLog(#"rows : %#", item);
for (BPCGAElements *element in item.elements) {
}
}
When I'm debugging the app, my first for each works without problems, but when I access the second foreach the (BPCGAElements * element in item.elements) the app crashes. Finally I can't print the item.elements in a log either. The error message is as follows:
'NSInvalidArgumentException', reason: '-[__NSSingleEntryDictionaryI
elements]: unrecognized selector sent to instance 0x281c75da0'
header BPCGADistance :
#interface BPCGADistance : MBOInspectableModel
#property (nonatomic, strong, readonly) NSString *text;
#property (nonatomic, strong, readonly) NSNumber *value;
#end
main BPCGADistance:
#interface BPCGADistance()
#property (nonatomic, strong, setter=text:) NSString *text;
#property (nonatomic, strong, setter=value:) NSNumber *value;
#end
header BPCGAElements :
#interface BPCGADistance : MBOInspectableModel
#property (nonatomic, strong, readonly) NSString *text;
#property (nonatomic, strong, readonly) NSNumber *value;
#end
main BPCGAElements:
#interface BPCGADistance()
#property (nonatomic, strong, setter=text:) NSString *text;
#property (nonatomic, strong, setter=value:) NSNumber *value;
#end
This is my NSLog(#"rows : %#", item),
I emphasize that by using the item.elements to print the crashea app on the console. ;
I am receiving below JSON response for saving user preferences in core data.
preferences={
1={
children=({
3=Samsung;4=Nokia;
});id=1;name=Mobiles;
};2={
children=({
5="Samsung Curve TV";
});id=2;name=Electronics;
};
};
Here is my code snippet which is working fine. But I think this is much verbose code.
NSLog(#"Preferences: %#", [response objectForKey:#"preferences"]);
for (NSDictionary *dic in [response objectForKey:#"preferences"]) {
NSLog(#"ID: %#", [[[response objectForKey:#"preferences"] objectForKey:dic] objectForKey:#"id"]);
NSLog(#"NAME: %#", [[[response objectForKey:#"preferences"] objectForKey:dic] objectForKey:#"name"]);
NSLog(#"Children DIC: %#", [[[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0] objectForKey:#"3"]);
for (NSDictionary *childDic in [[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0]) {
NSLog(#"Child Name: %#", [[[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0] objectForKey:childDic]);
}
}
I have 3 questions.
How can I improve my code snippet? is there any shorter way to implement this?
Is this JSON response is good enough for mobile end parsing? is it good JSON format? Is there any JSON response format we should follow as Mobile developers in terms of using Core data (which simply reduces DB implementation as best practice)?
How do I construct a JSON string like this again from Objective-c?
Ok so, (Sorry for NOT deep analysis) first of all I'd modify your JSON not to contain dictionary for dynamic elements (samsung has key 3 and so on, it should be array, make sense?)
I come up with imho better JSON structure:
{
"preferences":[
{
"items":[
{
"id" : "3",
"name" : "Samsung"
},
{
"id" : "3",
"name" : "Nokia"
}
],
"id":"1",
"name":"Mobiles"
},
{
"items":[
{
"id" : "3",
"name" : "Nokia"
}
],
"id":"2",
"name":"Electronics"
}
]
}
It's really easy now to map it to objects with JSONModel and only one thing you should care about is mapping keys.
NSString *JSONString = #" { \"preferences\":[ { \"items\":[ { \"id\" : \"3\", \"name\" : \"Samsung\" }, { \"id\" : \"3\", \"name\" : \"Nokia\" } ], \"id\":\"1\", \"name\":\"Mobiles\" }, { \"items\":[ { \"id\" : \"3\", \"name\" : \"Nokia\" } ], \"id\":\"2\", \"name\":\"Electronics\" } ] }";
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[JSONString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSError *mapError;
GlobalPreferences *globalPreferences = [[GlobalPreferences alloc] initWithDictionary:dictionary error:&mapError];
if (mapError) {
NSLog(#"Something went wrong with mapping model %#", mapError);
}
NSLog(#"Your mapped model: %#", globalPreferences);
Models:
Kinda GlobalPreference, root model, in case if u decide to add something extra
#import <JSONModel/JSONModel.h>
#import "Preference.h"
#interface GlobalPreferences : JSONModel
#property (nonatomic, strong) NSArray<Preference> *preferences; // using protocol here you specifying to which model data should be mapped
#property (nonatomic, strong) NSArray<Optional> *somethingElse; // some other settings may be here
#end
#import "GlobalPreferences.h"
#implementation GlobalPreferences
#end
Preference
#import <JSONModel/JSONModel.h>
#import "PreferenceItem.h"
#protocol Preference <NSObject>
#end
#interface Preference : JSONModel
#property (nonatomic, strong) NSNumber *ID; // required
#property (nonatomic, strong) NSString *name; // required
#property (nonatomic, strong) NSArray<PreferenceItem> *items;
#end
#import "Preference.h"
#implementation Preference
#pragma mark - JSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:#{
#"id": #"ID",
}];
}
#end
PreferenceItem
#import <JSONModel/JSONModel.h>
// This protocol just to let JSONModel know to which model needs to be parsed data in case if it's an array/dictionary
#protocol PreferenceItem <NSObject>
#end
#interface PreferenceItem : JSONModel
#property (nonatomic, strong) NSNumber *ID;
#property (nonatomic, strong) NSString *name;
#end
#import "PreferenceItem.h"
#implementation PreferenceItem
#pragma mark - JSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:#{
#"id": #"ID",
}];
}
#end
Should be ok for coreData.
Maybe for you case all this is not necessary, but there are lot of things you need to care when u parsing/mapping network response such es data types, missing keys, error handling and so on. If u map it manually - you risk to have broken app some day.
I know you asked best practices for Objective-C, but I would suggest switching to Swift and using SwiftyJSON. With Swift the code is much more readable than with Objective-C.
let prefs = json["preferences"]
let userName = prefs["id"].stringValue
let name = prefs["name"].stringValue
let child = prefs["children"].arrayValue[0].arrayValue[3]
https://github.com/SwiftyJSON/SwiftyJSON
You can easily build your own JSON structures and send them with Alamofire:
let parameters: [String : AnyObject] = [
"preferences": [
"id": "123",
"name": "John",
"children": [
["3": "Three"]
]
]
]
let urlReq = NSMutableURLRequest(url)
urlReq.HTTPMethod = .POST
let req = Alamofire.ParameterEncoding.JSON.encode(urlReq, parameters: parameters).0
let request = Alamofire.request(req)
https://github.com/Alamofire/Alamofire
I'm trying to get the subnodes of a JSON file usning githubs mantle.
This is what I tried:
JSON
"countries": {
"name": "germany",
"population": "80620000000",
"populationInCities": {
"Hamburg": 1799000,
"Berlin": 3502000,
"Munich": 1378000
}
}
CountryInfo.h
#import <Mantle/Mantle.h>
#interface CountryInfo : MTLModel <MTLJSONSerializing>
#property (nonatomic, readonly, copy) NSString *collectionName;
#property (nonatomic, readonly, assign) NSUInteger cPopulation;
#property (nonatomic, readonly, copy) NSArray *populationInCities;
#end
CountryInfo.m
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{ #"cName": #"name",
#"cPopulation": #"population",
#"populationInCities": [NSSet setWithArray:#[ #"Hamburg", #"Hamburg", #"Hamburg"]]
};
}
+ (NSValueTransformer *)populationInCitiesJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CountryInfo.class];
}
I'm getting an error when I run my APP:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'populationInCities must either map to a JSON key path or NSNull, got: {
populationInCities = (
Hamburg,
Berlin,
Munich
);
}.'
If you want to store population numbers in populationInCities array, you need a custom transformer:
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"collectionName": #"name",
#"cPopulation": #"population"};// 'populationInCities' names in JSON and model are the same, so no need to include here.
}
+ (NSValueTransformer *)populationInCitiesJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^(NSDictionary *dict) {
return [dict allValues];
}];
}
Embarrassing, but it was simly working using .-notation.
e.g.
popukationInCities.hamburg
If I have a dictionary like
{
name: "Bob",
cars: [
{ make: "ford", year: "1972" },
{ make: "mazda", year: "2000" }
],
}
and two models like:
#interface CarModel : MTLModel
#property (nonatomic, strong) NSString *make;
#property (nonatomic, strong) NSString *year;
#end
#interface PersonModel : MTLModel
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSArray *cars;
#end
How do I use Mantle so that my array of cars in my person model are CarModels?
Ah figured it out. I needed to add a private method:
+ (NSValueTransformer *)carsTransformer
{
return [NSValueTransformer mtl_externalRepresentationArrayTransformerWithModelClass:[CarModel class]];
}
and make sure I used:
[PersonModel modelWithExternalRepresentation:dict];
+[NSValueTransformer mtl_externalRepresentationArrayTransformerWithModelClass:] is deprecated. The new API is +[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:].
After switching to the new API, the models can be initialized with the default initializers provided by, e.g., MTLJSONAdapter.
A note on:
[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CarModel.class];
This methods seems to now be deprecated. I'm using this new method in my code and it appears to be working just fine:
[MTLJSONAdapter arrayTransformerWithModelClass:CarModel.class];
+ (NSValueTransformer *)carsJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CarModel.class];
}
Read more here
+[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:]
This method is deprecated. New method is:
+ (NSValueTransformer *)carsTransformer
{
return [MTLJSONAdapter arrayTransformerWithModelClass:[CarsModel class]];
}
I'm trying to map relationships for the following JSON to a CoreData backed model.
Here's the JSON:
{
"photos": [
{
"id": 1,
"thumb": "http://localhost/test_1_thumb.png",
"image": "http://localhost/test_1.png",
"date": "2011-07-02T06:06:16Z",
"likes": 0,
"user": {
"id": 1,
"username": "User1",
"avatar": "http://cdn.domain.com/avatar.jpg"
},
"comments": [
{
"date": "2011-07-02T06:06:16Z",
"text": "This is the only comment",
"id": 1,
"author": {
"username": "User1",
"id": 1,
"avatar": "http://cdn.domain.com/avatar.jpg"
}
}
]
}
]
}
My CoreData models which I've mapped with RestKit's OM2 classes.
The Photo class.
#interface Photo : NSManagedObject
#property (nonatomic, retain) NSString * thumbnailUrl;
#property (nonatomic, retain) NSString * imageUrl;
#property (nonatomic, retain) NSDate * date;
#property (nonatomic, retain) NSNumber * likes;
#property (nonatomic, retain) NSNumber * photoID;
#property (nonatomic, retain) User *owner;
#property (nonatomic, retain) NSSet *comments;
#end
#interface Photo (CoreDataGeneratedAccessors)
- (void)addCommentsObject:(Comment *)value;
- (void)removeCommentsObject:(Comment *)value;
- (void)addComments:(NSSet *)values;
- (void)removeComments:(NSSet *)values;
#end
#implementation Photo
#dynamic thumbnailUrl;
#dynamic imageUrl;
#dynamic date;
#dynamic likes;
#dynamic photoID;
#dynamic owner;
#dynamic comments;
#end
Now the part I'm not completely sure on is the relationships:
// Map relationships between entities.
[commentMapping mapKeyPath:#"author" toRelationship:#"author" withMapping:userMapping];
[photoMapping mapKeyPath:#"user" toRelationship:#"owner" withMapping:userMapping];
[photoMapping mapKeyPath:#"comments" toRelationship:#"comments" withMapping:commentMapping];
With this I'm able to access all but the comments attribute of a photo, with this error: Comments = Relationship 'comments' fault on managed object. I have a feeling this is to do with the Photo model having comments defined as an NSSet or I'm doing something wrong with the relationship mapping but I'm not sure.
So turns out that the problem was nothing to do with RestKit or the mapping but my understanding of how CoreData functions.
I was accessing the mapped JSON objects in my delgate as follows:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
NSLog(#"Loaded photos: %#", objects);
for (id object in objects) {
Photo *photo = object;
// Print other photo attributes...
NSLog(#"Comments = %#", photo.comments );
}
}
The problem was in trying to print the collection object directly. CoreData would not fire a fault on the data store. Simply replacing with a fast iteration as follows and accessing the properties allowed me to access the comments object.
for (Photo *photo in objects) {
for (Comment *comment in photo.comments) NSLog(#"Comment = %#", comment.text);
}
You need something like:
[objectManager.mappingProvider setMapping:commentMapping forKeyPath:#"comments"];
Also checkout
https://github.com/RestKit/RestKit/wiki/Object-mapping