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];
Related
I'm completely new to coredata few days back i started using core data. i have written a predicate to fetch data from coredata if i log data in current method it works fine. if i log data in another method it shows "data: "
<Profile: 0x16451ba0> (entity: Profile; id: 0x1633c150 <x-coredata://41971DAD-4658-4C38-9D14-7FDFFA57E032/Profile/p6> ; data: <fault>)
-(void)populateCurrentUserData{
self.blockListArray = [self dataForJid:[[DataManager sharedHandler]userToken]];
Profile *profile = [self.blockListArray objectAtIndex:0];
NSLog(#"Data is :%#",profile.userId);//prints nil
NSLog(#"Data is :%#",self.blockListArray); //"<Profile: 0x17bb23f0> (entity: Profile; id: 0x17bb1fe0 <x-coredata://41971DAD-4658-4C38-9D14-7FDFFA57E032/Profile/p1> ; data: <fault>)"
}
-(NSArray *)dataForJid:(NSString *)inJid{
NSArray *data = [[NSArray alloc]init];
NSError *error;
self.dataArray = [[NSMutableArray alloc]init];
MKAUserProfileCoreData *storage = [[MKAUserProfileCoreData alloc]init];
NSManagedObjectContext *moc = [storage managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Profile" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSPredicate *profilePredicate = [NSPredicate predicateWithFormat:#"userId = %#", inJid];
[request setPredicate:profilePredicate];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
data = [moc executeFetchRequest:request error:&error];
NSLog(#"Data is :%#",data); //This log works fine
return data;
}
/.h
#import <Foundation/Foundation.h>
#interface MKAUserProfileCoreData : NSObject
{
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
#property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#end
/.m
#import "MKAUserProfileCoreData.h"
#import <CoreData/CoreData.h>
#implementation MKAUserProfileCoreData
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"splashUserProfile.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} error:&error]) {
NSLog(#"Error is %#",error);
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should
not use this function in a shipping application, although it may be useful during
development. If it is not possible to recover from the error, display an alert panel that
instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object
model
Check the error message to determine what the actual problem was.
*/
abort();
}
return persistentStoreCoordinator;
}
- (NSString *)applicationDocumentsDirectory {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//NSLog(#"Path is %#", path);
return path;
}
#end
The problem is that both storage and moc are created locally in dataForJid:. When that method completes, both these variables will be deallocated. Hence the NSManagedObjects become invalid. You need to keep a strong reference to your CoreData stack - for example by making MKAUserProfileCoreData into a singleton, or by building the stack directly in your view controller.
Does your code work fine or have any issues running? Core data faults are not bad, they are a way of saving memory. A core data fault means that the entire object has not yet been loaded into memory. However, as soon as the object is requested, it will be loaded into memory.
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.
I need to update position in core data base. When I add object or remove it from that base everything works fine. Problem occurs when I want to update object in that base. The problem is that program throws me:
Thread 1: EXC_BAD_ACCES(code = 2, adres = 0x38).
It's weird because it throws this error but updtades object at core data. I'm using simulator on Xcode 5. I've tried to comment line by line back in code but it hasn't helped anymore. Taking look at the address of the error and the screen shot there is no address compatible with each other. What is wrong?
Code is this:
[self insertPositionRightAfterChangeValues:name rating:[changedRate stringValue] urlMain:[newInsert urlMain] contentUrl:[newInsert contentUrl] coverUrl:[newInsert coverUrl] date:[newInsert date] type:[newInsert type] int:integer];
And the rest of this method:
-(void)insertPositionRightAfterChangeValues:(NSString *)name rating:(NSString *)rating urlMain:(NSString *)urlMain contentUrl:(NSString *)contentUrl coverUrl:(NSString *)coverUrl date:(NSString *)date type:(NSString *)type int:(int)position
{
int motherPosision = position;
//insert new one
NSManagedObjectContext *context2 = [self managedObjectContext];
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:context2];
newEntry.name = [[NSString alloc] initWithString: name];
newEntry.rating = [[NSString alloc] initWithString: rating];
newEntry.urlMain = [[NSString alloc] initWithString: urlMain];
newEntry.contentUrl = [[NSString alloc] initWithString: contentUrl];
newEntry.coverUrl = [[NSString alloc] initWithString: coverUrl];
newEntry.date = [[NSString alloc] initWithString: date];
newEntry.type = [[NSString alloc] initWithString: type];
NSLog(#"%#", newEntry.name);
NSLog(#"%#", newEntry.rating);
NSLog(#"%#", newEntry.urlMain);
NSLog(#"%#", newEntry.contentUrl);
NSLog(#"%#", newEntry.coverUrl);
NSLog(#"%#", newEntry.date);
NSLog(#"%#", newEntry.type);
NSError *error2;
if (![context2 save:&error2]) {
NSLog(#"Whoops, couldn't save: %#", [error2 localizedDescription]);
}
else{
NSLog(#"SAVED!!!");
}
}
And the screenshot with this error:
UPDATE:
When I comment line where I invoke method to insert new object I get following error: CoreData: error: Failed to call designated initializer on NSManagedObject class 'Kwejki'
UPDATE 2:
#interface Kwejki : NSManagedObject<NSCopying>
#property (nonatomic, strong) NSString * type;
#property (nonatomic, strong) NSString * contentUrl;
#property (nonatomic, strong) NSString * coverUrl;
#property (nonatomic, strong) NSString * rating;
#property (nonatomic, strong) NSString * urlMain;
#property (nonatomic, strong) NSString * date;
#property (nonatomic, strong) NSString * name;
#end
#implementation Kwejki
#synthesize name;
#synthesize date;
#synthesize urlMain;
#synthesize rating;
#synthesize coverUrl;
#synthesize contentUrl;
#synthesize type;
-(Kwejki *)copyWithZone:(NSZone *)zone
{
Kwejki *copyModel = [[Kwejki allocWithZone:zone] init];
if(copyModel)
{
copyModel.name = [self name];
copyModel.date = [self date];
copyModel.urlMain = [self urlMain];
copyModel.rating = [self rating];
copyModel.coverUrl = [self coverUrl];
copyModel.contentUrl = [self contentUrl];
copyModel.type = [self type];
}
return copyModel;
}
#end
-(void)addNewPosition:(ScrollViewViewController *)ScrollController recentlyDownloadedItem:(KwejkModel *)modelTmp
{
if(modelTmp == nil)
{
NSLog(#"YES NULL");
}
else
{
NSLog(#"Not null");
}
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:self.managedObjectContext];
NSLog(#"%#", [modelTmp getNameOfItem]);
newEntry.name = [[NSString alloc] initWithString:[modelTmp getNameOfItem]];
newEntry.rating = [modelTmp getRateOfPosition];
newEntry.urlMain = [modelTmp getUrlAdress];
newEntry.contentUrl = [modelTmp getContentUrl];
newEntry.coverUrl = [modelTmp getCoverImage];
newEntry.date = [modelTmp getDateOfItem];
if([modelTmp getType] == photo)
{
newEntry.type = #"0";
}
else if([modelTmp getType] == gif)
{
newEntry.type = #"1";
}
else if ([modelTmp getType] == video)
{
newEntry.type = #"2";
}
NSLog(#"%#", newEntry.name);
NSLog(#"%#", newEntry.rating);
NSLog(#"%#", newEntry.urlMain);
NSLog(#"%#", newEntry.contentUrl);
NSLog(#"%#", newEntry.coverUrl);
NSLog(#"%#", newEntry.date);
NSLog(#"%#", newEntry.type);
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
else{
NSLog(#"UDALO SIE!!!");
}
[modelArray insertObject:newEntry atIndex:0];
[self.tableView reloadData];
}
I've tried to overcome this problem just adding new object again as update but it also doesn't helped. How I add new object: I tap long on position, new window opens and there I add new values, invoke from that view method from Main ViewController, and here it crashes. When I add normally object as described above but in separate window everything works. I really don't know what is wrong.
When I see this error it normally mean that I did not setup my NSManageObject correctly.
Things to check:
Add a break point in your insertPositionRightAfterChangeValues method and step through to find where the exception is thrown. (My guess is when your creating your Kwejki object.)
That Kwejki is a subclass of NSManageObject.
That Kwejki entity and class name are correct. (Could you show your .xcdatamodeId entity properties for Kwejki?)
If everything is correct, then for an insanity check I would create the Kwejki object the long way.
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Kwejki class]) inManagedObjectContext:context2];
Kwejki* newEntry = [[Kwejki alloc] initWithEntity:entity insertIntoManagedObjectContext:context2];
Then you can see if the entity is a problem or your Kwejki is.
P.S. As a helpful suggestion instead of typing in the entity name.
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:context2];
I would do something like this.
Kwejki * newEntry = [NSEntityDescription entityForName:NSStringFromClass([Kwejki class]) inManagedObjectContext:context2];
This give you a compiler check for entity name.
I created a Single View Application in iOS that also incorporates Core Data. I moved my .xcdatamodel file from another application and put in to the one I am working on now, and I am having issues. What I have done is cut and paste the code from the previous application and placed it in my AppDelegate.h/m files:
#interface DBAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#end
and my .m file:
#implementation DBAppDelegate
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
//the line below is what is causing an error
DBViewController *controller = (DBViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
Within my .m file, I also included the boiler plate code for Core Data that was also in my previous application which I have not posted. In my new application, what I am doing is that I have created an access layer, which also provides a Singleton instance to access this layer. It is in this class where I do my CRUD operations, and have declared the following properties in the .h file:
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
At the moment, I am getting the following error:
"Property 'managedObjectContext' not found on object of type "DBViewController". What I would like to do is initialize the managedObjectContext inside my method that allows for the creation of a Singleton instance:
static DB *sharedSingleton = nil;
+ (DB *) sharedInstance {
if (sharedSingleton == nil) {
sharedSingleton = [[super alloc] init];
}
return sharedSingleton;
}
What am I doing wrong? I realize I don't declare a managedObjectContext object in my DBViewController, but what do I put in place of this line? I figure it would be something with respect to my Singleton class, but I honestly don't have a clue here.
What you are trying to do is often called a "data store" singleton, and it's a good design pattern to use. What I do in my applications is have a singleton class named DataStore, you can call it whatever you wish, with a class method:
+ (id)sharedStore{
static DataStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[self alloc] init];
}
return sharedStore;
}
Whenever I need access to the resources provided by the datastore, I do:
Datastore *ds = [Datastore sharedStore];
To provide access to Core Data, I have a data store method:
+ (NSManagedObjectContext*)managedObjectContext{
static NSManagedObjectContext *context = nil;
if(context){
return context;
}
NSPersistentStoreCoordinator *coordinator = nil;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
NSManagedObjectModel *objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
if (!coordinator) {
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
}
if(!coordinator){
return nil;
}
NSString *storePath = [[self documentsDirectoryPath] stringByAppendingPathComponent:#"datastore.sqlite"];
NSURL *storeURL = [NSURL URLWithString:storePath];
NSError *error;
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:NULL error:&error])
{
NSLog(#"Database error: %#", error);
// if you make changes to your model and a database already exists in the app
// you'll get a NSInternalInconsistencyException exception. When the model is updated
// the databasefile must be removed. Remove the database here because it's easy.
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtURL:storeURL error:nil];
//try to add the persistant store one more time. If it still fails then abort
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:NULL error:&error])
return nil;
}
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
[context setUndoManager:nil];
return context;
}
This method returns nil if an NSManagedObjectContext couldn't be created. This way you only need to do:
NSManagedObjectContext *context = [[DataStore sharedStore] managedObjectContext];
whenever you need to use Core Data. This can be done once in viewDidLoad.
Edit:
The managedObjectContext method uses the method below to find the documents directory:
+ (NSString *)documentsDirectoryPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
I want to implement Core Data in my app with a Singleton Class that will manage the managed object context. I have the implementation without Core Data because I dont know how to manage everything.
Now, I have this code:
I have my ListViewController:
#interface YPProjectListViewController () {
SelectionSuccessBlock2 successBlock;
}
#property (nonatomic, strong) UITableView *tableView;
#property (nonatomic, strong) NSMutableArray * data;
#property (nonatomic, strong) UIRefreshControl *spinner ;
#end
#implementation YPProjectListViewController
#synthesize tableView;
#synthesize data;
#synthesize spinner;
- (void)viewDidLoad {
[super viewDidLoad];
spinner = [[UIRefreshControl alloc]initWithFrame:CGRectMake(130, 10, 40, 40)];
[self loadProjectsFromService];
[spinner addTarget:self action:#selector(loadProjectsFromService) forControlEvents:UIControlEventValueChanged];
[tableView addSubview:spinner];
}
-(void)loadProjectsFromService{
[spinner beginRefreshing];
self.data = [[NSMutableArray alloc] init];
[self.tableView reloadData];
[self.view addSubview:self.tableView];
__weak typeof(self) weakSelf = self;
successBlock = ^(NSMutableArray *newData) {
if ([newData count] > 0) {
[weakSelf refreshData:newData];
}
};
[spinner endRefreshing];
[ypNetManager getProjectListWithSuccessBlock:successBlock error:NULL];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Custom getter
- (UITableView *)tableView {
//custom init of the tableview
if (!tableView) {
// regular table view
tableView = [[UITableView alloc] initWithFrame:UIEdgeInsetsInsetRect(self.view.bounds, tableViewInsets) style:UITableViewStylePlain];
tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
tableView.delegate = self;
tableView.dataSource = self;
tableView.backgroundColor = [UIColor clearColor];
return tableView;
}
return tableView;
}
#pragma mark - Private methods
- (void)refreshData:(NSMutableArray *)newData {
self.data = newData;
[self.tableView reloadData];
}
I have my AFNetworking AFHttpClient subclass with my method :
- (void)getProjectListWithSuccessBlock:(SelectionSuccessBlock2)success error:(SelectionErrorBlock)error {
NSMutableURLRequest *request = [self requestWithMethod:#"GET" path:kAPIProjectListDev parameters:nil];
[request setTimeoutInterval:kTimeOutRequest];
AFJSONRequestOperation *requestOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// NSLog(#"%# Susscess JSNON Response: %#", NSStringFromSelector(_cmd),JSON);
NSMutableArray * data = [[NSMutableArray alloc] init];
NSDictionary *projects = [JSON valueForKey:kTagProjects];
for (NSDictionary *projectDic in projects) {
[data addObject:[Project createProjectWithDictionary:projectDic]];
}
if (success) {
success(data);
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *aError, id JSON) {
NSLog(#"%# Failure JSNON Error%#", NSStringFromSelector(_cmd), aError);
if (error) {
error(aError);
}
}];
[self enqueueHTTPRequestOperation:requestOperation];
}
The object Project :
#property (nonatomic, strong) NSNumber *projectId;
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSNumber *estimatedPrice;
and its category helper:
#implementation Project (Helper)
+ (Project *)createProjectWithDictionary:(NSDictionary *)dic {
Project *project = [[Project alloc] init];
project.projectId = [NSNumber numberWithInt:[[dic valueForKey:kTagProjectId] intValue]];
project.title = [dic valueForKey:kTagProjectTitle];
project.estimatedPrice = [NSNumber numberWithInt:[[dic valueForKey:kTagProjectEstimatedPrice] floatValue]];
// NSLog(#"Project ..... %d, Title: %#", [project.projectId intValue], project.title);
return project;
}
#end
Now, I want to use my DataSingleton Class to manage my ListViewController and my object projects in a TableView. I have the singleton. but I dont understand quite well where I have to start the managedObjectContext. or where I have to reload the TableView..
//
// DataSingleton.m
// Yeeplys
//
// Created by Carlos Roig Salvador on 8/31/13.
// Copyright (c) 2013 Carlos Roig Salvador. All rights reserved.
//
#import "DataSingleton.h"
//static instance for singleton implementation
static DataSingleton __strong *manager = nil;
//Private instance methods/properties
#interface DataSingleton()
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and
// bound to the persistent store coordinator for the application.
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's
// store added to it.
#property (readonly,strong,nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory;
#end
#implementation DataSingleton
#synthesize managedObjectContext = __managedObjectContext;
#synthesize managedObjectModel = __managedObjectModel;
#synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
//DataAccessLayer singleton instance shared across application
+ (id)sharedInstance
{
#synchronized(self)
{
if (manager == nil)
manager = [[self alloc] init];
}
return manager;
}
+ (void)disposeInstance
{
#synchronized(self)
{
manager = nil;
}
}
+(NSManagedObjectContext *)context
{
return [[DataSingleton sharedInstance] managedObjectContext];
}
//Saves the Data Model onto the DB
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
//Need to come up with a better error management here.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and
// bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
return __managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the
// application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
return __managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"yeeplyModel"
withExtension:#"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc]
initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the
// application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
return __persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory]
URLByAppendingPathComponent:#"MyData.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
}
#end
To reload your table view you can attach an NSFetchedResultsController (link to Apple docs) to your view controller in order to get automatic updates every time you update your NSManagedObjectContext. There is a code in this repository made by Javi Soto that is easy to understand in order to implement the NSFetchedResultsController in your project. Take a look at JSBaseCoreDataTableViewController here.