I would like to save the NSMutableArray to Core-Data when my application terminates/goes into background and I would like to load the NSMutableArray when my application launches/becomes active.
I don't have a very good understanding of Core-Data yet. This is my first time working with it. I've looked at a bunch of videos, tutorials, previous Stackoverflow questions and Apple's documentation. I think what I am trying to do falls under the Non-Standard Persistent Attributes chapter in Apple's Core-Data documentation.
I have set up an Entity called TableViewList and I have given it an attribute called List of type transformable.
Here is my AppDelegate.h and .m code. All advice would be wonderful.
AppDelegate.h
#import <UIKit/UIKit.h>
#import "TableViewController.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property(nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property(nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property(nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
-(NSString *) applicationDocumentsDirectory;
#end
AppDelegate.m
#import <CoreData/CoreData.h>
#import "AppDelegate.h"
#import <UIKit/UIKit.h>
#interface AppDelegate ()
#end
#implementation AppDelegate
#synthesize managedObjectModel;
#synthesize managedObjectContext;
#synthesize persistentStoreCoordinator;
- (void)applicationDidEnterBackground:(UIApplication *)application {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newContact;
newContact = [NSEntityDescription insertNewObjectForEntityForName:#"TableViewList" inManagedObjectContext:context];
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:ListArray];
[newContact setValue:arrayData forKey:#"list"];
NSError *error = nil;
[context save:&error];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSURL *url = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent: #"App1.sqlite"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:nil];
managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType];
managedObjectContext.persistentStoreCoordinator = coordinator;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableViewList" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"unable to execute fetch request");
NSLog(#"%#, %#", error, error.localizedDescription);
}
else
NSLog(#"%#",result);
}
The result array returns an empty array. I don't think I'm saving and fetching the array correctly. Thanks in advance for your help!
I used this link to implement NSCoding in my object.
OK there are several things to mention here:
In applicationDidEnterBackground, the first half of the method configures a new managed object context that you never use. Since you then get a different managed object context from the app delegate, you don't the one you create here, so the code that creates a new context can be deleted. You probably also want to use the app delegate's context in applicationDidBecomeActive, though what you have isn't necessarily wrong.
You don't ever save changes. You need to call save: on the managed object context to save data to the persistent store file.
In order to use a transformable property, the data you're saving must conform to NSCoding (because Core Data doesn't know how to transform arbitrary classes, and NSCoding is how you tell it what to do). NSArray does, but it's also important that everything in the array also conforms. If your custom class does that, you're OK. If not, you'll need to fix that to save the array or find a different way to save your data.
I don't believe that you're going to get a mutable array back, no matter what you do. Once you get saving and fetching working, you'll get an immutable array as the value of the list property. So you'll need to call mutableCopy if you need the array to be mutable.
Related
I am getting some crashes that I have never experienced myself during test, development or usage.
I can see them on Fabric Dashboard, and it concerns the NSManagedObjectContext.
Here is the first call on StackTrace :
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:[MyAppDelegate mainContext]];
CDFavori is a class representing the CoreData object, which is extended in order to implement some methods (in order to fetch) :
+(CDFavori *)favoriWithIndicatif:(NSString*)indicatif context:(NSManagedObjectContext*)context
{
if (nil == indicatif || nil == context)
return nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"CDFavori"];
[request setPredicate:[NSPredicate predicateWithFormat:#"indicatif LIKE %#", indicatif]];
NSError *error = nil;
NSArray *favoris = [context executeFetchRequest:request error:&error];
CDFavori *fav = nil;
if (nil != error) {
DDLogError(#"Error = %# (%#)", indicatif, error);
} else if (0 < [favoris count])
{
fav = [favoris objectAtIndex:0];
if (1 < [favoris count]) {
DDLogWarn(#"More than one object present in DB : %#", indicatif);
}
}
return favori;
}
The Crashes don't come from this method, it is just to give you some context.
The issue comes from the AppDelegate and the NSManagedObjectContext.
Here are my code for the Core Data methods :
+(NSManagedObjectContext*)mainContext
{
return ((MyAppDelegate*)[UIApplication sharedApplication].delegate).managedObjectContext;
}
The crashes are here :
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
EDIT - Just to mention the declarations :
in header :
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
in .m file:
#pragma mark - Core Data stack
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
EDIT After Answer :
Do you think something like this would be better ?
Remove this declaration :
#pragma mark - Core Data stack
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
And replace it with :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
_managedObjectModel = [[NSManagedObjectModel alloc] init];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] init];
}
This would be better ? With the same .h file.
But I have to change all my variables, and change the readonly properties ?
I believe that you have 3 separate issues:
ARC is conspiring against you
try replacing:
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:[MyAppDelegate mainContext]];
with
NSManagedObjectContext* context = [MyAppDelegate mainContext];
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:context];
I had a similar issue and this fixed it. The reason is that when the context is created and then pass directly as a parameter ARC release the context on the next line. Then the managedObject has no context and it crashes. If you first assign it to a local variable then ARC will keep the context around for the entire scope. This cannot happen in a debug environment because ARC behaves differently there.
You are not doing multithreading correctly
The next question is why is the context being release. While you haven't shown any wrong code, I suspect that this is happening very early in the application lifecycle and that there are multiple threads creating a the main context at the same time. So the first call creates a context and assigns it to _managedObjectContext and then a second context gets assigned and the first context gets release. (And it is not retained in the local scope so there is a crash).
In your core data setup you should ONLY access the _managedObjectContext variable on the main thread. I suggest adding a check at the beginning of the managedObjectContext method
if (![NSThread mainThread]) {
// log error to fabric
//[[Crashlytics sharedInstance] recordError:...];
return nil;
}
Lazily creating core-data can lead to errors
Also I would create the _managedObjectContext explicitly on launch in application:didFinishLaunchingWithOptions: and not create it lazily. When it is created lazily you don't know when exactly it will be created. And if it is created from a background thread your entire stack will be messed up. You have little to gain from doing it lazily as you are certainly going to create it in order to show anything to the user.
You could leave your code as is and simply add
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self managedObjectContext]; //force loading of context
In Apple's Core Data example 'TaggedLocations' they have a ViewController that declares this in the header
#property (nonatomic) NSManagedObjectContext *managedObjectContext;
Then in the main file they never set that context equal to anything or initialize it or allocate it anywhere. They simply use it in a fetch request to retrieve results. This makes sense given that the context is simply a "scratchpad' for objects from the persistent store.
However, I don't see how they're declaring which or any persistent store either. In previous Core Data examples I've always seen people create instances of AppDelegate and access it's context and store, which makes sense because in this example the entire core data stack is there.
This is the snippet of code from Apple's example, what am I over looking?
/*
Fetch existing events.
Create a fetch request for the Event entity; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"APLEvent"];
[request setFetchBatchSize:20];
// Order the events by creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[request setSortDescriptors:sortDescriptors];
// Execute the fetch.
//Not sure how they're excuting a fetch request on self.managedObjectContext? Seems to be a nil unintialized context
//TO-DO Test value of context
NSError *error;
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:request error:&error];
if (fetchResults == nil) {
// 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Link to Apple's example https://developer.apple.com/library/ios/samplecode/TaggedLocations/Introduction/Intro.html#//apple_ref/doc/uid/DTS40008914
Thanks ahead for any input!
Whenever project created with core data , In app delegate u can get these,
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
and you can get external file too, where your data going to store.
I have a DataManager class which returns a shared instance:
+ (DataManager *)sharedInstance;
{
static DataManager *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [[DataManager alloc] init];
});
return sharedInstance;
}
In here I keep track of my managedObjectContext, managedObjectModel, persistentStoreCoordinator.
I also have a method where I pull out items for displaying:
- (NSArray *)getItems
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Item"];
return [[self managedObjectContext] executeFetchRequest:fetchRequest error:nil];
}
Now in my main app I have a view controller when I call this getItems and then modify items individually. So for example set item.itemName = #"testName"; and then call my save method.
I also have an iOS 8 where in my TodayViewController I also call the getItems method. I have an NSNotification which detects for managedObjectContext saves.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refetchItems) name:NSManagedObjectContextDidSaveNotification object:[[DataManager sharedInstance] managedObjectContext]];
These refetched items does get called but returns the outdated NSManagedObjects. So for example the itemName has not changed to #"testName".
Where am I going wrong? Let me know if you need to see any other code.
Thanks!
You may try the following for refreshing particular ManagedObject. And if you want to refresh a list of ManagedObject then loop each object and execute the command.
[_managedObjectContext refreshObject:act mergeChanges:YES];
Or for iOS verion 8.3 and above you can make use of the following method for updating all the ManagedObject in the context at once as follows.
[_managedObjectContext refreshAllObjects];
It works a bit, but only for data UPDATE, not for adding or deleting data.
If it is not working, you can add also
[_managedObjectContext reset];
after that, you have to read "reassign" all variables, that you have loaded from your core data store.
Another solution (slower and more ugly)
If the above is not working, another solution would be to delete current context and create it again.
I just set
_persistentStoreCoordinator = nil;
_managedObjectModel = nil;
_managedObjectContext = nil;
I have CoreDataManager class with this properties
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
And in class I have manually created setters. If I nill all variables, due to setters, they are inited again once I read them outside my core data manager class.
You can improve this by using NSUserDefault store. It is being updated correctly. In main app, if you change smething, set flag in NSUserDefault. In extension, read this and if flag is marked, reset core data. This way, you will save some ticks and make things faster a bit.
For allocation of NSUserDefault (in both apps - extension and main) use this - after that, you can read data from it as usuall and they should be in sync
NSUserDefaults *prefs = [[NSUserDefaults alloc] initWithSuiteName:GROUP_NAME]; //share with extension
I use a singleton for working with arrays etc. cross the views in the application.
To initialize the singleton and the NSManagedObjectContext, so that I can fetch objects, I use:
+(DataControllerSingleton *)singleDataController
{
static DataControllerSingleton * single=nil;
#synchronized(self)
{
if(!single)
{
single = [[DataControllerSingleton alloc] init];
NSManagedObjectContext *context = [single.fetchedResultsController managedObjectContext];
single.masterCareList = [[NSMutableArray alloc] init];
}
}
return single;
}
When I insert a new object that object will not show up in display functions until I restart the application. I insert new object through this method in the singleton class:
- (void)insertNewObject:(Care *)care
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:
[entity name] inManagedObjectContext:self.managedObjectContext];
NSString *fileName = care.pictureURL;
NSString *text = care.causeText;
NSDate *date = care.date;
NSData *imgData = care.imageData;
[newManagedObject setValue:fileName forKey:#"urlPath"];
[newManagedObject setValue:text forKey:#"name"];
[newManagedObject setValue:date forKey:#"date"];
[newManagedObject setValue:imgData forKey:#"imageData"];
// Save the context.
[self saveContext];
NSError *error = nil;
if (![context save:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
My count method is the way I can tell that the new object is not included until I restart the application. The count method is also in the singleton as well.
- (NSUInteger)countOfList
{
NSArray *fetchedData = [_fetchedResultsController fetchedObjects];
return [fetchedData count];
}
When calling singleton I use:
DataControllerSingleton *singletonData = [DataControllerSingleton singleDataController];
[singletonData insertNewObject:care];
managedObjectContext property:
.h:
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
.m:
#implementation DataControllerSingleton
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
Why will not my new object show up in ex count until I restart application?
Am I somehow using different threads with different contexts, or different fethedResultsController or different singleton (shouldnt be possible right?)??
I added these two lines, which are not included in the genereated CoreData Stack, and it now works fine.
In singleton header:
#interface DataControllerSingleton : NSObject <NSFetchedResultsControllerDelegate>
In implementation file,
(NSFetchedResultsController *)fetchedResultsController {
_fetchedResultsController.delegate = self;
As I understand from your question, you are using a table or similar.
If you want to update the table as soon as you save the context you need to:
Reload the data table [table reloadData];
or implement in the correct delegate methods of (take a look to How To Use NSFetchedResultsController)
If you follow the first option, you can just do a save in the context and call realod data on the table.
NSError *error = nil;
if (![context save:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[table reloadData];
NOTE THAT YOU ARE CALLING THE SAVE TWICE Do it once. In this case I suppose that [self saveContext]; does the saving as above.
If you follow the second approach, the data reload woul be handled for you.
Hope that helps.
Edit
The delegate of your fetched results controller should be a view controller (the one that contains the table). Do not put it in your singleton!!
I am showing data in table view using NSFetchedResultsController. Now when data reaches from server I need to delete all data present in the sqlite database.
Now when I delete data from database using given below code it sometime crashes (not always) giving this error:
Execution_BAD-ACCESS (code=2, address=0x0)
on this line
if (![moc save:&saveError]) {
.h
#property (readonly, retain, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, retain, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, retain, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
.m
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
NSManagedObjectContext *moc = [delegate managedObjectContext];
NSFetchRequest * allCategories = [[NSFetchRequest alloc] init];
[allCategories setEntity:[NSEntityDescription entityForName:#"Categories" inManagedObjectContext:moc]];
[allCategories setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * dataArray = [moc executeFetchRequest:allCategories error:&error];
//error handling goes here
[NSFetchedResultsController deleteCacheWithName:#"RootDetail"];
for (Categories *cat in dataArray) {
[moc deleteObject:cat];
}
NSError *saveError = nil;
if (![moc save:&saveError]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[allCategories release];
I check throughly now i found that this problem is coming when i vist the DetailPageController and go back(using UINavigationController popNavigationController:) and then if i wist DetailPageController then it crashes.
giving following errror
-[DetailPageController controllerWillChangeContent:]: message sent to deallocated instance 0x11f52a90*
The problem is of NSManageObjectContext. So the fix is always use new created object of NSManageObjectContext otherwise it will create problems.
Based on your comment
I am using operation queue. so i enter data on main thread. 2. you are
saying that each thread should have separated instance of context. But
i think there should be only one main instance of context.
No. You MUST follow the documentation about Concurrency with Core Data
Create a separate managed object context for each thread and share a
single persistent store coordinator. This is the typically-recommended
approach.
or
Create a separate managed object context and persistent store
coordinator for each thread. This approach provides for greater
concurrency at the expense of greater complexity (particularly if you
need to communicate changes between different contexts) and increased
memory usage.
or
use new Core Data APIs.
Original question
If you provide some other details about the crash, I think we can help you. What about delegate?
In the meantime, some hints for you.
1) Enable zombies in Xcode
How to enable NSZombie in Xcode?
2) Use the right context
Why do you use the following?
NSManagedObjectContext *moc = [delegate managedObjectContext];
just use
NSManagedObjectContext *moc = [self managedObjectContext];
This could be the origin of the problem. But without details I'm not very sure.
So, when you create this controller from external, set the managed object context property correctly.
yourController.managedObjectContext = theContextYouWantToShare;
3) Error handling
NSError * error = nil;
NSArray * dataArray = [moc executeFetchRequest:allCategories error:&error];
if(dataArray) {
// manage objects here...
} else {
// handle error here...
}
The answer by flexaddicted is very good (unfortunately I can't comment yet) but remember to be very careful if you have a multi-threaded application (you mention server calls in your question). Make sure that each thread uses its own context, otherwise you will run into problems. This is well docmented in Apple's Core Data documentation.
Or, at the very least, make sure that any call to do something with core data is on the main thread (although this is not ideal as this can block when performing long operations).