Try to implement usage of CoreData with RestKit.
Problem - got message I restkit.core_data:RKInMemoryManagedObjectCache.m:94 Caching instances of Entity 'ItemRK' by attributes 'itemID' and crash. Try to add Exception on errors- cant catch it.
Check mapping - OK;
Check DBModel - OK; empty DB created successfully
I cant understand what was done wrong and where, so I put here main parts of code (sorry for long explanation).
What was done:
I try to organize my code like proposed here.
So i create RKBaseObjectManager for using RestKit
#implementation RKBaseObjectManager
#pragma mark - Public
+ (instancetype)sharedManager
{
RKBaseObjectManager *sharedManager = [self managerWithBaseURL:[NSURL URLWithString:kBaseApiUrl]];
sharedManager.requestSerializationMIMEType = RKMIMETypeJSON;
[sharedManager setupRequestDescriptor];
[sharedManager setupResponseDescriptor];
[sharedManager setupCoreDataWithRKObjectManager:sharedManager];
return sharedManager;
}
- (void)setupRequestDescriptor { }
- (void)setupResponseDescriptor { }
#pragma mark - Private
- (void)setupCoreDataWithRKObjectManager:(RKObjectManager *)manager
{
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
manager.managedObjectStore = managedObjectStore;
[managedObjectStore createPersistentStoreCoordinator];
NSString *dbName = [NSString stringWithFormat:#"%#.sqlite", kDataBaseName];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:dbName];
NSError *error;
NSPersistentStore *persistentStore = [managedObjectStore
addSQLitePersistentStoreAtPath:storePath
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:#{NSMigratePersistentStoresAutomaticallyOption : #YES, NSInferMappingModelAutomaticallyOption: #YES}
error:&error];
NSAssert(persistentStore, #"Failed to add persistent Store with error - %#", error.localizedDescription);
[managedObjectStore createManagedObjectContexts];
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
}
#end
Then for each entity create inherited class with singleton - ItemManager
#implementation ItemManager
#pragma mark - Singleton
+ (instancetype)sharedManager
{
static ItemManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^ {
sharedManager = [super sharedManager];
});
return sharedManager;
}
#pragma mark - Public
- (void)loadItemWithId:(NSInteger)itemId success:(ObjectSuccess)success failure:(ObjectFailure)failure
{
NSDictionary *parameters = #{ #"item_id" : #(itemId) };
[self getObjectsAtPath:kItemsDetailsPathPattern parameters:parameters success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
if (success) {
success(mappingResult.array);
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
if (failure) {
failure (operation, error);
}
}];
}
#pragma mark - Private
- (void)setupResponseDescriptor
{
[super setupResponseDescriptor];
NSIndexSet *statusCode = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *itemDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:[MappingProvider mappingForItemRKEntity]
method:RKRequestMethodGET
pathPattern:kItemsDetailsPathPattern
keyPath:nil
statusCodes:statusCode];
[self addResponseDescriptor:itemDescriptor];
}
#end
For mapping I add next class - MappingProvider
#implementation MappingProvider
#pragma mark - EntityMapping
+ (RKEntityMapping *)mappingForItemRKEntity
{
RKEntityMapping *itemEntityMapping = [RKEntityMapping mappingForEntityForName:MPItemEntityName inManagedObjectStore:[RKBaseObjectManager sharedManager].managedObjectStore];
itemEntityMapping.identificationAttributes = #[#"itemID"];
[itemEntityMapping addAttributeMappingsFromDictionary:[self mappingDictionaryForItem]];
return itemEntityMapping;
}
+ (RKObjectMapping *)mappingForItemRK
{
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[ItemRK class]];
[itemMapping addAttributeMappingsFromDictionary:[self mappingDictionaryForItem]];
return itemMapping;
}
#pragma mark - Private
+ (NSDictionary *)mappingDictionaryForItem
{
NSDictionary *dictionary = #{
#"item_id" : #"itemID",
#"name" : #"name",
#"description" : #"itemDescription",
#"type" : #"type",
#"level" : #"level",
#"rarity" : #"rarity",
#"vendor_value" : #"vendorValue",
#"icon_file_id" : #"iconFileId",
#"icon_file_signature" : #"iconFileSignature",
#"default_skin" : #"defaultScin"
};
return dictionary;
}
And when I want to get object -
[[ItemManager sharedManager] loadItemWithId:28445 success:^(NSArray *mappingResult) {
ItemRK *result = (ItemRK *)[mappingResult firstObject];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Fail to load - %#", error.localizedDescription);
}];
Test entity class (autoGenerated)
#interface ItemRK : NSManagedObject
#property (nonatomic, retain) NSNumber * defaultScin;
#property (nonatomic, retain) NSNumber * flags;
#property (nonatomic, retain) NSData * gameTypes;
#property (nonatomic, retain) NSNumber * iconFileId;
#property (nonatomic, retain) NSString * iconFileSignature;
#property (nonatomic, retain) NSString * itemDescription;
#property (nonatomic, retain) NSNumber * itemID;
#property (nonatomic, retain) NSNumber * level;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * rarity;
#property (nonatomic, retain) NSData * restrictions;
#property (nonatomic, retain) NSString * type;
#property (nonatomic, retain) NSNumber * vendorValue;
#property (nonatomic, retain) WeaponRK *weapon;
#end
So the problem is as I wrote at beggining - something with core_data:RKInMemoryManagedObjectCache. Also try to found similar problems:
To many unique keys added - not the reason - I use only one itemEntityMapping.identificationAttributes = #[#"itemID"];
To many request - not the reason - only one request;
API for my example - https://api.guildwars2.com/v1/item_details.json?item_id=28445 - checked with Postman - OK.
So think I was wrong somewhere in mapping and combining it to CoreData - but cant find where.
Question - where i make mistake? Why i cant combine RestKit and CoreData?
Finally found the reason -
When I create ItemManager I also automatically try to add response descriptor (with this method setupResponseDescriptor), last use mapping, and mapping use 'BaseObjectManager' as it managedObjectStore the is a parent for ItemManager. So during creating ItemManager I create infinity cycle - as result program crashed.
Solution - I create response descriptor in class where I call ItemManager like
[[ItemManager sharedManager] addResponseDescriptor:[ResponseDescriptorProvider itemResponseDescriptor]];
[[ItemManager sharedManager] loadItemWithId:28450 success:^(NSArray *result) {
NSArray *arout = result;
NSLog(#"Result: Item - %# and nested Weapon - %#", (ItemRK *)[arout firstObject], ((WeaponRK *)((ItemRK *)[arout firstObject]).weapon).type );
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error response': %#", error);
}];
Also i moved out response descriptor from manager to separate class - ResponseDescriptorProvider
+ (RKResponseDescriptor *)itemResponseDescriptor
{
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider mappingForItemRKEntity]
method:RKRequestMethodGET
pathPattern:kItemsDetailsPathPattern
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
return responseDescriptor;
}
The rest leave the same.
Thanks all for help.
Related
am new to iOS, Getting issue with displaying data from below service data
[{
"Name": Rahul,
"FatherName": Ravinder,
"Designation": Engineering,
"Profession": Software Eng,
"Height": "5 ft 3 in",
"Weight": "134.5 lbs"
}]
below is the code what i have tried. Please help me to find the issue. Thanks In Advance.
NameDetails.m
---------------
- (void)viewDidLoad {
[super viewDidLoad];
[self callService:[appDelegate.signUpdata objectForKey:#"id"]];
}
-(void)callService:(NSString *)userid
{
[Utility showIndicator:nil view1:self.view];
JsonServicePostData = [[JsonServiceCls alloc] init];
JsonServicePostData.delegate = self;
[JsonServicePostData Getdata:userid];
}
-(void)DidFinishWebServicesPostData
{
[Utility hideIndicator];
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
_txtName.text=[dict objectForKey:#"Name"];
_txtFName.text=[dict objectForKey:#"FatherName"];
_txtDesg.text=[dict objectForKey:#"Designation"];
_txtprof.text=[dict objectForKey:#"Profession"];
_txtHeight.text=[dict objectForKey:#"Height"];
_txtWeight.text=[dict objectForKey:#"Weight"];
}
}
+(void)makeHttpGETresponceParsingwithSerVer:(NSString *)strServer withCallBack:(void(^)(NSDictionary *dicArr,NSError *error))handler
{
NSURL *urlServer = [NSURL URLWithString:strServer];
NSURLRequest *request = [NSURLRequest requestWithURL:urlServer];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
handler(res,error);
}];
[postDataTask resume];
}
then call your method In viewDidLoad...
[RestClient makeHttpGETresponceParsingwithSerVer:#"YOUR_URL" withCallBack:^(NSDictionary *responceDic, NSError *error) {
_txtName.text =[responceDic objectForKey:#"Name"];
_txtFName.text =[responceDic objectForKey:#"FatherName"];
_txtDesg.text =[responceDic objectForKey:#"Designation"];
_txtprof.text =[responceDic objectForKey:#"Profession"];
_txtHeight.text =[responceDic objectForKey:#"Height"];
_txtWeight.text =[responceDic objectForKey:#"Weight"];
}];
// RestClient is the class name as it is a class method, You can use instance method.
Hi The better approach is for this kind of API call activity you have to go with AFNetworking - https://github.com/AFNetworking/AFNetworking
Its Pretty simple and more powerful. Once you get the json response you have to go for Model Approach.
#import <UIKit/UIKit.h>
#interface NameDetails : NSObject
#property (nonatomic, strong) NSString * designation;
#property (nonatomic, strong) NSString * fatherName;
#property (nonatomic, strong) NSString * height;
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * profession;
#property (nonatomic, strong) NSString * weight;
-(instancetype)initWithDictionary:(NSDictionary *)dictionary;
-(NSDictionary *)toDictionary;
#end
#import "RootClass.h"
NSString *const kRootClassDesignation = #"Designation";
NSString *const kRootClassFatherName = #"FatherName";
NSString *const kRootClassHeight = #"Height";
NSString *const kRootClassName = #"Name";
NSString *const kRootClassProfession = #"Profession";
NSString *const kRootClassWeight = #"Weight";
#interface RootClass ()
#end
#implementation RootClass
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
-(instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if(![dictionary[kRootClassDesignation] isKindOfClass:[NSNull class]]){
self.designation = dictionary[kRootClassDesignation];
}
if(![dictionary[kRootClassFatherName] isKindOfClass:[NSNull class]]){
self.fatherName = dictionary[kRootClassFatherName];
}
if(![dictionary[kRootClassHeight] isKindOfClass:[NSNull class]]){
self.height = dictionary[kRootClassHeight];
}
if(![dictionary[kRootClassName] isKindOfClass:[NSNull class]]){
self.name = dictionary[kRootClassName];
}
if(![dictionary[kRootClassProfession] isKindOfClass:[NSNull class]]){
self.profession = dictionary[kRootClassProfession];
}
if(![dictionary[kRootClassWeight] isKindOfClass:[NSNull class]]){
self.weight = dictionary[kRootClassWeight];
}
return self;
}
/**
* Returns all the available property values in the form of NSDictionary object where the key is the approperiate json key and the value is the value of the corresponding property
*/
-(NSDictionary *)toDictionary
{
NSMutableDictionary * dictionary = [NSMutableDictionary dictionary];
if(self.designation != nil){
dictionary[kRootClassDesignation] = self.designation;
}
if(self.fatherName != nil){
dictionary[kRootClassFatherName] = self.fatherName;
}
if(self.height != nil){
dictionary[kRootClassHeight] = self.height;
}
if(self.name != nil){
dictionary[kRootClassName] = self.name;
}
if(self.profession != nil){
dictionary[kRootClassProfession] = self.profession;
}
if(self.weight != nil){
dictionary[kRootClassWeight] = self.weight;
}
return dictionary;
}
The above one is Model Class.
Your JSON look like a array. So you need to iterate the Dictionary values on it. Other than that you may pass it directly.
Now in your ViewController class initiate the mutable array
and pass the response like
NSArray *arrayData = ResponseFromAFNETWORKING
for (NSDictionary *data in arrayData) {
NameDetails *modelFeed = [[NameDetails alloc] initFromDictinary:data]
[self.YourMutableDictionary addObject:modelFeed]
}
self.updateDisplay:self.YourMutableDictionary[0] // If not array No iteration, you can prepare the model and pass it directly
----------------------------------------
- (void)updateDisplay:(NameDetails *)feed {
_txtName.text =feed.Name;
_txtFName.text =feed.FatherName;
_txtDesg.text =feed.Designation;
_txtprof.text =feed.Profession;
_txtHeight.text =feed.Height;
_txtWeight.text =feed.Weight;
}
Hope this will help. This is a robust and elastic approach, thread safe mechanism too
so I have an object with several properties that I want to add to coredata however I'm using the Mantle framework that I am not too familiar with.
My Object(.h):
#import <Foundation/Foundation.h>
#import "RTModel.h"
#import CoreLocation;
#class RTPhoto;
#class RTContact;
#class RTUser;
#interface MyInteraction : RTModel <NSCopying, MTLJSONSerializing>
+ (NSManagedObject *)managedObjectWithIdentifier:(NSNumber *)identifier;
- (NSManagedObject *)managedObject;
#property (nonatomic, strong) NSNumber *latitude;
#property (nonatomic, strong) NSNumber *longitude;
#property (strong, nonatomic) RTUser *user;
#property (nonatomic, strong) NSNumber *createdByID;
#property (nonatomic, copy) NSString *createdByLabel;
#property (nonatomic, copy) NSString *interactedWithLabel;
#property (strong, nonatomic) NSString *publicShareURLString;
#property (nonatomic, strong) NSSet *photos;
- (void)addPhotosObject:(RTPhoto *)photo;
#end
My Object(.m):
#import "OUTInteraction.h"
#import "RTComment.h"
#import "RTPhoto.h"
#import "RTUser.h"
#import "RTContact.h"
#import "OUTCoreDataController.h"
#import "OUTInteractionsController.h"
#interface MyInteraction () <UIActionSheetDelegate>
#end
#implementation MyInteraction
#synthesize identifier = _identifier;
#synthesize createdAt = _createdAt;
#synthesize updatedAt = _updatedAt;
- (void)deleteWithCompletion:(void (^)(NSError *))completion
{
NSManagedObject *managedObject = [self managedObject];
if (!managedObject) {
completion(nil);
};
[[[MyCoreDataController sharedController] persistenceController] deleteObject:managedObject
saveContextAndWait:YES
completion:^(NSError *error) {
completion(error);
}];
}
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return [[super JSONKeyPathsByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary: #{
#"createdByID" : #"created_by_id",
#"createdByLabel" : #"created_by_label",
#"interactedWithLabel" : #"interacted_with_label",
#"interactionType" : #"interaction_type",
#"latitude" : #"latitude",
#"longitude" : #"longitude",
#"formID" : #"form_id",
#"formEntries" : #"form_entries",
#"interactedWithCity" : #"interacted_with_city",
#"interactedWithRegion" : #"interacted_with_region",
#"publicShareURLString" : #"share_url",
#"photos" : #"images"
}];
}
#pragma mark - Photos
- (void)addPhotosObject:(RTPhoto *)photo
{
if (!photo) return;
NSMutableSet *mutablePhotos = [NSMutableSet setWithSet:self.photos];
[mutablePhotos addObject:photo];
self.photos = [mutablePhotos copy];
}
- (BOOL)hasPhotoToDisplay
{
if (!self.photos) return NO;
if ([[self.photos allObjects] count] > 0) return YES;
return NO;
}
#pragma mark - JSON Transformers
/// #note used by Mantle
+ (NSValueTransformer *)photosJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^id(NSArray *array) {
NSMutableSet *mutableSet = [NSMutableSet set];
[array enumerateObjectsUsingBlock:^(NSDictionary *JSON, NSUInteger idx, BOOL *stop) {
NSError *error = nil;
RTPhoto *item = [MTLJSONAdapter modelOfClass:[RTPhoto class]
fromJSONDictionary:JSON
error:&error];
if (error) DDLogError([error description], nil);
[mutableSet addObject:item];
}];
NSLog(#"mutable set");
return mutableSet;
} reverseBlock:^id(NSSet *set) {
NSLog(#"all objects");
return [set allObjects];
}];
}
/// #note used by Mantle
+ (NSValueTransformer *)commentsJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^id(NSArray *array) {
NSMutableSet *mutableSet = [NSMutableSet set];
[array enumerateObjectsUsingBlock:^(NSDictionary *JSON, NSUInteger idx, BOOL *stop) {
NSError *error = nil;
RTComment *item = [MTLJSONAdapter modelOfClass:[RTComment class]
fromJSONDictionary:JSON
error:&error];
if (error) DDLogError([error description], nil);
[mutableSet addObject:item];
}];
return mutableSet;
} reverseBlock:^id(NSSet *set) {
return [set allObjects];
}];
}
/// #note used by Mantle
+ (NSValueTransformer *)contactsJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^id(NSArray *array) {
NSMutableSet *mutableSet = [NSMutableSet set];
[array enumerateObjectsUsingBlock:^(NSDictionary *JSON, NSUInteger idx, BOOL *stop) {
NSError *error = nil;
RTComment *item = [MTLJSONAdapter modelOfClass:[RTContact class]
fromJSONDictionary:JSON
error:&error];
if (error) DDLogError([error description], nil);
[mutableSet addObject:item];
}];
return mutableSet;
} reverseBlock:^id(NSSet *set) {
return [set allObjects];
}];
}
/// #note used by Mantle
+ (NSValueTransformer *)formEntriesJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^id(NSArray *entryDictionaries) {
NSMutableSet *set = [NSMutableSet set];
[entryDictionaries enumerateObjectsUsingBlock:^(NSDictionary *entryDict, NSUInteger idx, BOOL *stop) {
NSError *error = nil;
RTInteractionFormEntry *entry = [MTLJSONAdapter modelOfClass:[RTInteractionFormEntry class]
fromJSONDictionary:entryDict
error:&error];
[set addObject:entry];
}];
return set;
} reverseBlock:^id(NSSet *set) {
NSMutableArray *array = [NSMutableArray array];
[set enumerateObjectsUsingBlock:^(RTInteractionFormEntry *entry, BOOL *stop) {
[array addObject:[entry jsonDictionary]];
}];
return array;
}];
}
/// #note used by Mantle
+ (NSValueTransformer *)userJSONTransformer
{
return [MTLValueTransformer mtl_JSONDictionaryTransformerWithModelClass:[RTUser class]];
}
/// #note used by Mantle
+ (NSValueTransformer *)interactionTypeJSONTransformer
{
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:#{
#"email" : #(kRTInteractionTypeEmail),
#"check_in" : #(kRTInteractionTypeCheckIn),
#"note" : #(kRTInteractionTypeNote),
#"meeting" : #(kRTInteractionTypeMeeting)
}];
}
#pragma mark - Core Data
+ (NSString *)managedObjectEntityName
{
return #"Interaction";
}
+ (NSSet *)propertyKeysForManagedObjectUniquing
{
return [NSSet setWithArray:#[#"identifier"]];
}
+ (NSDictionary *)managedObjectKeysByPropertyKey
{
return [[super managedObjectKeysByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary:#{
#"metadata" : [NSNull null],
#"identifier" : #"interactionIdentifier",
}];
}
+ (NSDictionary *)relationshipModelClassesByPropertyKey
{
return [[super relationshipModelClassesByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary:#{
#"comments" : [RTComment class],
#"formEntries" : [RTInteractionFormEntry class],
#"photos" : [RTPhoto class],
#"contacts" : [RTContact class],
#"user" : [RTUser class]
}];
}
#pragma mark - Actions
- (void)delete
{
[[MyInteractionsController controller] deleteInteraction:self
success:^{
} failure:^(NSError *error) {
DDLogError([error description], nil);
}];
}
+ (NSManagedObject *)managedObjectWithIdentifier:(NSNumber *)identifier
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[MyInteraction managedObjectEntityName]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"interactionIdentifier == %#", identifier];
fetchRequest.fetchLimit = 1;
NSManagedObjectContext *managedObjectContext = [[MyCoreDataController sharedController] managedObjectContext];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest
error:&error];
NSLog(#"Results: %#", results);
if (error) {
DDLogError(#"Error fetching managed object: %#", [error description]);
}
return [results firstObject];
}
- (NSManagedObject *)managedObject
{
return [MyInteraction managedObjectWithIdentifier:self.identifier];
}
- (void)mergeValuesForKeysFromManagedObject:(NSManagedObject *)managedObject
{
if ([managedObject valueForKey:#"photos"]) {
NSMutableSet *combinedPhotos = [NSMutableSet setWithSet:self.photos];
for (NSManagedObject *photoManagedObject in [managedObject valueForKey:#"photos"]) {
RTPhoto *photo = [MTLManagedObjectAdapter modelOfClass:[RTPhoto class]
fromManagedObject:photoManagedObject
error:nil];
if (photo.identifier || photo.localImageData || photo.imageDataPendingUpload) {
[combinedPhotos addObject:photo];
}
}
self.photos = [combinedPhotos copy];
}
}
#end
Basically the problem I'm having when adding the whole object to CoreData
via Mantle is that everything gets added except when it reaches the photos property which is a NSSet it only adds one of the objects, usually the last one.
Here is when the object is added to the context using Mantle(in another file):
[MTLManagedObjectAdapter managedObjectFromModel:interaction
insertingIntoContext:self.context
error:&coreDataError];
If someone has any clue as to why there is only one photos object being added under the "photos" key. I'm unfamiliar with how mantle handles toMany relationships and how to get all of the children added. Thanks
I have my Core Data object which has some properties like:
#interface ModuleCD : NSManagedObject
#property (nonatomic, retain) NSString * image;
#property (nonatomic, retain) NSNumber * latitude;
#property (nonatomic, retain) NSDate * locationTimestamp;
#property (nonatomic, retain) NSNumber * moduleId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSNumber * status;
#property (nonatomic, retain) NSOrderedSet *openPositions;
#property (nonatomic, retain) NSSet *slaves;
#property (nonatomic, retain) NSOrderedSet *tracks;
#end
The problem is that I have many and many objects in openPositions and tracks. There are thousands positions saved and each track could have many positions too. My problem is that when I update "simple" values of ModuleCD (first six values, but mainly only timestamp is changed) it takes too long. It looks like it's recreating openPositions and tracks and so but there wasn't changes in there. So where could be problem? I am using 3 layer Core Data model (private temp context, main UI context and writer private contenxt which has set persistent store). It blocks UI for several seconds and it just changes timestamp.
Edit:
My update method (I update it from another similar object which is not related to core data):
#implementation ModuleCD (Ext)
- (void)updateValues:(Module *)module
{
...
self.locationTimestamp = module.locationTimestamp;
...
}
My save context methods:
- (void)saveTempContext:(NSManagedObjectContext *)context withCompletion:(void (^)(NSError *error))completion {
NSError *error;
[context save:&error];
if (!error) {
[self saveMainContextCompletion:^(NSError *error) {
completion(error);
}];
} else {
completion(error);
}
}
- (void)saveMainContextCompletion:(void (^)(NSError *error))completion {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if ([self.managedObjectContext hasChanges]) {
[self.managedObjectContext save:&error];
if(!error){
//Write to disk after saving on the main UI context
[self saveWriterContextCompletion:^(NSError *error) {
completion(error);
}];
} else {
completion(error);
}
} else {
completion(nil);
}
}];
}
- (void)saveWriterContextCompletion:(void (^)(NSError *error))completion {
[self.writerManagedObjectContext performBlock:^{
NSError *error = nil;
[self.writerManagedObjectContext save:&error];
if (error){
NSLog(#"CORE DATA MASTER CONTEXT ERROR : %#", error);
completion(error);
} else {
completion(nil);
}
}];
}
I update context everytime when user uses pull-to-refresh on tableView and I get response from server.
- (void)updateModulesWithModules:(NSArray *)newModules completion:(void (^)(NSError *error))completion
{
__block NSManagedObjectContext *managedObjectContext = [[CoreDataManager manager] managedObjectContext];
__block NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = managedObjectContext;
[temporaryContext performBlock:^{
NSArray *modules = [[CoreDataManager manager]fetchArrayWithoutSubentitiesWithName:#"ModuleCD" andPredicate:nil inManagedObjectContext:temporaryContext];
///
...
here some changes, calling updateValues method
...
///
[[CoreDataManager manager] saveTempContext:temporaryContext withCompletion:^(NSError *error) {
completion(error);
}];
}];
}
Here is my JSON
{
"pages": 8,
"salads": [
{
"id": "392",
"img": "http://salatiki.com.ua/images/mini/20120114_285.jpg",
"ingredCount": 5,
"ingredients": "картофель, свекла, салат, яйцо, сливки",
"name": "Шведский картофельный",
"rating": 4
},
.......
]
}
I got "pages", but my "salads" is nil (see log).
My NSManagedObjects
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class SSaladsBody;
#interface SSaladPage : NSManagedObject
#property (nonatomic, retain) NSNumber * sPages;
#property (nonatomic, retain) SSaladsBody *salads;
#end
for key "salads"
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface SSaladsBody : NSManagedObject
#property (nonatomic, retain) NSNumber * sId;
#property (nonatomic, retain) NSString * sImage;
#property (nonatomic, retain) NSString * sName;
#property (nonatomic, retain) NSNumber * sRating;
#property (nonatomic, retain) NSString * sIngredients;
#property (nonatomic, retain) NSNumber * sIngredientsCount;
#end
in my add delegate
RKEntityMapping *saladPageMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([SSaladPage class]) inManagedObjectStore:managedObjectStore];
[saladPageMapping addAttributeMappingsFromDictionary:#{ #"pages" : #"sPages", }];
saladPageMapping.identificationAttributes = #[#"sPages"];
RKEntityMapping *saladsBodyMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([SSaladsBody class]) inManagedObjectStore:managedObjectStore];
[saladsBodyMapping addAttributeMappingsFromDictionary:#{ #"id" : #"sId",
#"img" : #"sImage",
#"name" : #"sName",
#"rating" : #"sRating",
#"ingredients" : #"sIngredients",
#"ingredCount" : #"sIngredientsCount" }];
saladsBodyMapping.identificationAttributes = #[#"sId"];
[saladPageMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"salads"
toKeyPath:#"salads"
withMapping:saladsBodyMapping]];
RKResponseDescriptor *saladPageDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:saladPageMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptorsFromArray: #[saladPageDescriptor]];
[[RKObjectManager sharedManager] getObjectsAtPath:#"api/get.php?getByCat=1&page=1" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Mapping salads OK! %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#",error);
}];
and log
2014-11-04 23:57:49.641 Salatiki[10213:f03] I restkit.network:RKObjectRequestOperation.m:250 GET 'http://salatiki.com.ua/api/get.php?getByCat=1&page=1' (200 OK / 1 objects) [request=0.0989s mapping=0.0117s total=0.1613s]
2014-11-04 23:57:49.641 Salatiki[10213:607] Mapping salads OK! (
"<SSaladPage: 0x7b03b970> (entity: SSaladPage; id: 0x7b267d90 <x-coredata://8CC2FD2A-9D05-4863-99AA-30B318EEC57C/SSaladPage/p42> ; data: {\n sPages = 8;\n salads = nil;\n})"
)
what i'm doing wrong? I try to add one more responseDeskriptor, for "salads" it work's but he put array in to main {}; not to "salads" = []; Thank you.
The property
#property (nonatomic, retain) SSaladsBody *salads;
should actually be a relationship, so it should be NSSet, not SSaladsBody.
Other than that, things look ok. If you still have issues you should turn on trace logging and see what processing is done around the relationship contents.
I'm working on my diploma project, which includes an iOS client with a Core Data database and a Ruby on Rails server. I'm using RestKit for the communication between them. Currently I'm having a big issue getting the whole system to work: as I try to map a response to objects from the server, I get the following exception:
2013-02-08 22:40:43.947 App[66735:5903] *** Assertion failure in -[RKManagedObjectResponseMapperOperation performMappingWithObject:error:], ~/Repositories/App/RestKit/Code/Network/RKResponseMapperOperation.m:358
2013-02-08 23:04:30.562 App[66735:5903] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx)'
*** First throw call stack:
(0x1de9012 0x1c0ee7e 0x1de8e78 0x16a4f35 0x8f56e 0x8d520 0x1647d23 0x1647a34 0x16d4301 0x23a253f 0x23b4014 0x23a52e8 0x23a5450 0x90ac6e12 0x90aaecca)
libc++abi.dylib: terminate called throwing an exception
I'm trying to load a list (an array) of contacts from the server, which should be saved as "Users" in Core Data.
I've structured all my Core Data code in a Data Model class, like I saw in this video: http://nsscreencast.com/episodes/11-core-data-basics. Here it is:
Header file:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface AppDataModel : NSObject
+ (id)sharedDataModel;
#property (nonatomic, readonly) NSManagedObjectContext *mainContext;
#property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)modelName;
- (NSString *)pathToModel;
- (NSString *)storeFilename;
- (NSString *)pathToLocalStore;
#end
Implementation file:
#import "AppDataModel.h"
#interface AppDataModel ()
- (NSString *)documentsDirectory;
#end
#implementation AppDataModel
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
#synthesize mainContext = _mainContext;
+ (id)sharedDataModel {
static AppDataModel *__instance = nil;
if (__instance == nil) {
__instance = [[AppDataModel alloc] init];
}
return __instance;
}
- (NSString *)modelName {
return #"AppModels";
}
- (NSString *)pathToModel {
return [[NSBundle mainBundle] pathForResource:[self modelName]
ofType:#"momd"];
}
- (NSString *)storeFilename {
return [[self modelName] stringByAppendingPathExtension:#"sqlite"];
}
- (NSString *)pathToLocalStore {
return [[self documentsDirectory] stringByAppendingPathComponent:[self storeFilename]];
}
- (NSString *)documentsDirectory {
NSString *documentsDirectory = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
return documentsDirectory;
}
- (NSManagedObjectContext *)mainContext {
if (_mainContext == nil) {
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_mainContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
}
return _mainContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel == nil) {
NSURL *storeURL = [NSURL fileURLWithPath:[self pathToModel]];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:storeURL];
}
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator == nil) {
NSLog(#"SQLITE STORE PATH: %#", [self pathToLocalStore]);
NSURL *storeURL = [NSURL fileURLWithPath:[self pathToLocalStore]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *e = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&e]) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:e forKey:NSUnderlyingErrorKey];
NSString *reason = #"Could not create persistent store.";
NSException *exc = [NSException exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:userInfo];
#throw exc;
}
_persistentStoreCoordinator = psc;
}
return _persistentStoreCoordinator;
}
#end
The User class is pretty straightforward, auto-generated with xCode.
Header file:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface User : NSManagedObject
#property (nonatomic, retain) NSString * email;
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * lastName;
#property (nonatomic, retain) NSNumber * userID;
#end
Implementation file:
#import "User.h"
#implementation User
#dynamic email;
#dynamic firstName;
#dynamic lastName;
#dynamic userID;
#end
Just like the data model class, I have a server manager class which I use for communication:
Header file:
#import <Foundation/Foundation.h>
#import <RestKit/RestKit.h>
#import "AppServerProtocol.h"
#import "AppDataModel.h"
#interface AppServer : NSObject <AppServerDelegate>
+ (id)sharedInstance;
#property (strong, nonatomic) RKObjectManager *objectManager;
#property (strong, nonatomic) RKEntityMapping *userMapping;
#end
And implementation file:
#import "AppServer.h"
#import "User.h"
#import "Device.h"
#import "Ping.h"
#import "AppAppDelegate.h"
#interface AppServer ()
#property BOOL initialized;
#end
#implementation AppServer
+ (id)sharedInstance {
static AppServer *__instance = nil;
if (__instance == nil) {
__instance = [[AppServer alloc] init];
__instance.initialized = NO;
}
if (![__instance initialized]) {
[__instance initServer];
}
return __instance;
}
- (void)initServer {
// initialize RestKit
NSURL *baseURL = [NSURL URLWithString:#"http://localhost:3000"];
_objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// enable activity indicator spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
// initialize managed object store
_objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];
_userMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:_objectManager.managedObjectStore];
[_userMapping addAttributeMappingsFromDictionary:#{
#"email" : #"email",
#"firstName" : #"first_name",
#"lastName" : #"last_name"
}];
[_userMapping setIdentificationAttributes: #[#"userID"]];
RKResponseDescriptor *contactsResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:_userMapping pathPattern:#"/contacts" keyPath:nil statusCodes:nil];
[_objectManager addResponseDescriptor:contactsResponseDescriptor];
_initialized = YES;
}
// contacts
- (void)getContactsForCurrentUser {
NSString *authToken = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppAuthenticationToken"];
[_objectManager getObjectsAtPath:#"/contacts" parameters:#{#"auth_token": authToken} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of contacts: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
}
#end
So when I open the Contacts Table View, which is set up correctly to use a fetched results controller (successfully pulling entities out of the DB), I have a dangerous refresh button, which calls the method you've just read above:
- (void)downloadContacts {
[[AppServer sharedInstance] getContactsForCurrentUser];
}
Here is the format of the response:
[
{
"created_at":"2013-01-11T14:03:57Z",
"email":"john#example.com",
"first_name":"John",
"id":2,
"last_name":"Doe",
"updated_at":"2013-02-07T10:57:16Z"
},
{
"created_at":"2013-01-11T14:03:57Z",
"email":"jane#example.com",
"first_name":"Jane",
"id":3,
"last_name":"Doe",
"updated_at":"2013-02-07T10:57:16Z"
}
]
And before the exception the console states the following:
2013-02-08 22:40:36.892 App[66735:c07] I restkit:RKLog.m:34 RestKit logging initialized...
2013-02-08 22:40:36.994 App[66735:c07] SQLITE STORE PATH: ~/Library/Application Support/iPhone Simulator/6.0/Applications/D735548F-DF42-4E13-A7EF-53DF0C5D8F3B/Documents/AppModels.sqlite
2013-02-08 22:40:37.001 App[66735:c07] Context is ready!
2013-02-08 22:40:43.920 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:154 GET 'http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx'
2013-02-08 22:40:43.945 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:181
The line of the RestKit library, that fails before the whole exception is thrown is:
NSAssert(self.managedObjectContext, #"Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = %#)", self.response.URL);
I have followed that back to the initServer method in the AppServer.m file, in which, before the method returns, the properties of the RKObjectManager class are like this: http://imgur.com/LM5ZU9m
As I have debugged, I've traced that the problem is not with the server side or the communication of the app - I can see the JSON received and deserialized into an array, but the moment it's passed to the next method which is supposed to save it to Core Data, the whole app goes kaboom because of the NSAssert of the managed object context.
Any help is greatly appreciated!
After a few days of debugging, I finally found out what went wrong: it looks like I also had to set the path to my local persistence store and generate managed object contexts for the managed object store myself.
Here's where I found the solution: https://github.com/RestKit/RestKit/issues/1221#issuecomment-13327693
I've just added a few lines in my server init method:
NSError *error = nil;
NSString *pathToPSC = [[AppDataModel sharedDataModel] pathToLocalStore];
_objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];
[_objectManager.managedObjectStore addSQLitePersistentStoreAtPath:pathToPSC fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
if (error != nil) {
NSLog(#"\nSerious object store error!\n");
return;
} else {
[_objectManager.managedObjectStore createManagedObjectContexts];
}
I managed to do it using this function RKApplicationDataDirectory() to get the application directory and set my database path.
// Initialize HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://myapiaddress.com"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
//we want to work with JSON-Data
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"NameOfMyCoreDataModel" ofType:#"momd"]];
//Iniitalize CoreData with RestKit
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"nameOfDB.sqlite"];
objectManager.managedObjectStore = managedObjectStore;
[objectManager.managedObjectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
[objectManager.managedObjectStore createManagedObjectContexts];