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.
Related
I have a sample app that has a single NSManagedObject (let's call the class CustomObject) with a single property.
The app has a simple UI consisting of a view controller with a UICollectionView using an NSFetchedResultsController as the underlying data source. The NSFetchedResultsController is also setup to use the view controller as it's NSFetchedResultsControllerDelegate delegate. Additionally I have toolbar with some buttons to add/remove CustomObject items.
One button will result in 10,000 CustomObject entities being created. I'd like to be able to press this button, and once the entities are saved to the disk/ready to be used, the UICollectionView should update to reflect the new values. I am 100% aware that there will be a delay of some kind - saving values like this is not instantaneous - I want the delay to occur on a background thread to prevent the UI from locking up!
Despite me setting things up correctly from what I can tell, I experience UI lag whenever I implement any of the three "important" NSFetchedResultsControllerDelegate methods. The UI lag duration varies based on the number of CustomObjects in the data store already. For example if I just added the first 10,000, there is only a small amount of lag, less than 1 second. If I've added 50,000 CustomObjects already the lag is much more noticeable and lasts for over 5-10 seconds in some cases.
As I said, the UI lag only shows up when I implement either the controllerWillChangeContent, controller:didChangeObject:atIndexPath:forChangeType:newIndexPath, or controllerDidChangeContent methods. Even with no actual implementation in the method just the fact that it is in the view controller makes the insertion slow.
Any idea what I am doing wrong? Is there a way around this? Here's some code:
CoreDataController.m - responsible for setting up the core data stack
//
// CoreDataController.m
// CoreDataTestApp
//
// Created by ZOlbrys on 4/12/18.
// Copyright © 2018 ZOlbrys. All rights reserved.
//
#import "CoreDataController.h"
#import <UIKit/UIKit.h>
#interface CoreDataController()
#property (nonatomic, strong) NSURL *persistentStoreURL;
#property (nonatomic, strong) NSURL *managedObjectModelURL;
#property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
#end
#implementation CoreDataController
+ (CoreDataController *)sharedController {
static CoreDataController *sharedController;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURL *managedObjectModelURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:#"CoreDataTestApp" ofType:#"momd"]];
NSString *applicationDocumentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *persistentStoreURL = [NSURL fileURLWithPath:[applicationDocumentsDirectory stringByAppendingPathComponent:#"CoreDataTestApp.sqlite"]];
sharedController = [[CoreDataController alloc] initWithManagedObjectModelURL:managedObjectModelURL persistentStoreURL:persistentStoreURL];
});
return sharedController;
}
- (instancetype)initWithManagedObjectModelURL:(NSURL*)managedObjectModelURL persistentStoreURL:(NSURL*)persistentStoreURL {
self = [super init];
if (self) {
self.persistentStoreURL = persistentStoreURL;
self.managedObjectModelURL = managedObjectModelURL;
[self setupCoreDataStack];
}
return self;
}
- (void)setupCoreDataStack {
// setup managed object model
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.managedObjectModelURL];
// setup persistent store coordinator
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.persistentStoreURL options:nil error:nil];
// setup MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
#end
ViewController.m - responsible for, well, view controller stuff!
//
// ViewController.m
// CoreDataTestApp
//
// Created by ZOlbrys on 4/12/18.
// Copyright © 2018 ZOlbrys. All rights reserved.
//
#import "ViewController.h"
#import "CustomCell.h"
#import "CoreDataController.h"
#import "CustomObject+CoreDataClass.h"
static NSString *const CUSTOM_CELL_REUSE_IDENTIFIER = #"CUSTOM_CELL_REUSE_IDENTIFIER";
#interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self setupFetchedResultsController];
}
- (void)setupFetchedResultsController {
NSFetchRequest<CustomObject *> *fetchRequest = [CustomObject fetchRequest];
fetchRequest.sortDescriptors = #[ [[NSSortDescriptor alloc] initWithKey:#"identifier" ascending:YES] ];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[CoreDataController sharedController].managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:nil];
[self.collectionView reloadData];
}
- (void)addObjects:(NSUInteger)objectCount {
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = [CoreDataController sharedController].managedObjectContext;
[temporaryContext performBlock:^{
for (int i = 0; i < objectCount; i++) {
CustomObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"CustomObject" inManagedObjectContext:temporaryContext];
object.identifier = [[NSUUID UUID] UUIDString];
}
NSError *error;
if (![temporaryContext save:&error]) {
NSLog(#"Error: %#", error);
}
[[CoreDataController sharedController].managedObjectContext performBlock:^{
NSError *error;
if (![[CoreDataController sharedController].managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
}];
}];
}
- (void)removeObjects:(NSUInteger)objectCount {
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = [CoreDataController sharedController].managedObjectContext;
[temporaryContext performBlock:^{
NSFetchRequest<CustomObject *> *fetchRequest = [CustomObject fetchRequest];
fetchRequest.sortDescriptors = #[ [[NSSortDescriptor alloc] initWithKey:#"identifier" ascending:YES] ];
fetchRequest.fetchLimit = objectCount;
NSFetchedResultsController<CustomObject *> *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:temporaryContext sectionNameKeyPath:nil cacheName:nil];
[fetchedResultsController performFetch:nil];
for (CustomObject *object in fetchedResultsController.fetchedObjects) {
[temporaryContext deleteObject:object];
}
NSError *error;
if (![temporaryContext save:&error]) {
NSLog(#"Error: %#", error);
}
[[CoreDataController sharedController].managedObjectContext performBlock:^{
NSError *error;
if (![[CoreDataController sharedController].managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
}];
}];
}
- (IBAction)add10k:(id)sender {
[self addObjects:10000];
}
- (IBAction)add1:(id)sender {
[self addObjects:1];
}
- (IBAction)refetchData:(id)sender {
[self.fetchedResultsController performFetch:nil];
[self.collectionView reloadData];
}
- (IBAction)remove1:(id)sender {
[self removeObjects:1];
}
- (IBAction)remove10k:(id)sender {
[self removeObjects:10000];
}
#pragma mark UICollectionViewDataSource
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.fetchedResultsController.sections objectAtIndex:section].numberOfObjects;
}
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
CustomCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:CUSTOM_CELL_REUSE_IDENTIFIER forIndexPath:indexPath];
[cell setDisplayText:#"TODO"];
cell.backgroundColor = [UIColor blueColor];
return cell;
}
#pragma mark NSFetchedResultsControllerDelegate
//- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// // TODO
//}
//
//- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
// // TODO
//}
//
//- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// // TODO
//}
#end
Of course, I did some profiling - with controllerWillChangeContent uncommented above, I see this:
Which is not my code - it's internal Apple code...
Here's a github link to a project showing this issue:
Sample Core Data project
The lag seems to be related to the chosen Core Data stack:
[Coordinator] ← [Main Context] ← [Private Context]
With this architecture, each fetch/save operations on the private context will fall through the main context and then block the UI. A better choice could be to swap the contexts:
[Coordinator] ← [Private Context] ← [Main Context]
This time, private context operations will not block the UI.
Another option is to bind both contexts to the coordinator:
[Private Context] → [Coordinator] ← [Main Context]
With both solutions, you will need to merge back updated objects to the main context if needed, in order to "see" the changes from the UI.
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.
My code here i am trying to add list of object in to my array form that array i trying to add it to code data attributes.
#import "ViewController.h"
#import "model.h"
#import "coredataManager.h"
#interface ViewController ()
{
NSMutableArray *entries;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
entries = [[NSMutableArray alloc]init];
coredataManager *coreobj = [[coredataManager alloc]init];
model *obj = [[model alloc]initWithContents:#"1" alternateLink:#"1" DownloadURL:#"1"];
model *obj2 = [[model alloc]initWithContents:#"2" alternateLink:#"2" DownloadURL:#"2"];
model *obj3 = [[model alloc]initWithContents:#"3" alternateLink:#"3" DownloadURL:#"3"];
[entries addObject:obj];
[entries addObject:obj2];
[entries addObject:obj3];
NSLog(#"%#",entries);
[coreobj StoreValues:entries];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
my NSObject Class
#import <Foundation/Foundation.h>
#interface model : NSObject
#property(copy)NSString *filename;
#property(copy)NSString *alternatelink;
#property(copy)NSString *downloadurl;
-(id)initWithContents:(NSString *)Fname alternateLink :(NSString *) ALink DownloadURL :(NSString *)DURL;
#end
NSObject Implementation File
#import "model.h"
#implementation model
#synthesize downloadurl,filename,alternatelink;
-(id)initWithContents:(NSString *)Fname alternateLink :(NSString *) ALink DownloadURL :(NSString *)DURL
{
if ((self = [super init])) {
downloadurl = [DURL copy];
filename = [Fname copy];
alternatelink = [ALink copy];
}
return self;
}
#end
Code to store in core data
-(void)StoreValues:(NSMutableArray *) sample
{
Sample *value = [NSEntityDescription insertNewObjectForEntityForName:#"Sample"
inManagedObjectContext:self.managedObjectContext];
for (int i=0;i<sample.count;i++)
{
model *obj=[sample objectAtIndex:i];
value.url = obj.downloadurl;
value.filename = obj.filename;
value.alternate = obj.alternatelink;
}
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
But only the last value of the array is getting stored in the core data can any one guide me solve this issue
Thanks in advance.
Before you save, you rewrite the object. Do this instead:
Sample *value;
for (int i=0;i<sample.count;i++)
{
value = [NSEntityDescription insertNewObjectForEntityForName:#"Sample"
inManagedObjectContext:self.managedObjectContext];
model *obj=[sample objectAtIndex:i];
value.url = obj.downloadurl;
value.filename = obj.filename;
value.alternate = obj.alternatelink;
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
That saves the object after every iteration and doesn't rewrite it. Then starts anew.
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'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];