Core Data iOS 8 Today Widget issue - ios

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

Related

iOS - Crash on NSManagedObjectContext

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

iOS 8+ Core Data Faults When Resuming App From Background

This issue only occurs in iOS 8 or higher. I have a core data managed object that I'm assigning to the property of a UIViewController. Whenever I leave the app and resume it from the background, the managed object faults. Whenever I attempt to access a property on the object, the fault does not fire and all of the data returns nil.
I set an observer for UIApplicationWillEnterForegroundNotification to examine the selectedObject, and at that point in the code execution the object has not faulted yet. It only faults following the app entering the foreground. Does anyone have any idea what could be happening here?
Edit #1:
Here's more of the relevant files. Note these have been simplified and variable names changed to protect the original code:
myAppDelegate.h
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
myAppDelegate.m
#synthesize managedObjectContext = __managedObjectContext;
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
// This code only gets hit the first time the app tries to access the context.
// After that (including when the app resumes from the background), the one stored in __managedObjectContext is returned.
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
DetailView.h
#property (retain, nonatomic) MyObject *selectedObject;
DetailView.m
#synthesize selectedObject;
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didBecomeActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
- (void)willEnterForeground
{
NSLog(#"App will enter foreground."); // breakpoint here
}
- (void)didBecomeActive
{
NSLog(#"App became active."); // breakpoint here
}
- (void)viewWillAppear:(BOOL)animated
{
NSManagedObjectContext *context = ((myAppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSError *error = nil;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyObject" inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(selected == YES)", nil];
[request setEntity:entity];
[request setPredicate:predicate];
selectedObject = (MyObject*)[[context executeFetchRequest:request error:&error] objectAtIndex:0];
}
- (IBAction)buttonPressed:(id)sender
{
NSString *link = [selectedObject link]; // breakpoint here
}
When I hit the breakpoints in willEnterForeground and didBecomeActive I check the selectedObject:
MyObject: 0x7fa801c80bc0 (entity: MyObject; id: 0xd000000020940002 x-coredata://9775DE4D-2312-4684-904B-613302AC2B19/MyObject/p2085 ; data: { link = "http://www.example.com" }
I also check [selectedProperty managedObjectContext] and the [myAppDelegate managedObjectContext], both give me the following:
NSManagedObjectContext: 0x7fa7fbc96c90
Now if I click the button that is bound to buttonPressed: and re-check everything, [myAppDelegate managedObjectContext] still gives me the same output above, but [selectedObject managedObjectContext] is nil and examining the object gives the following:
MyObject: 0x7fa801c80bc0 (entity: MyObject; id: 0xd000000020940002 x-coredata://9775DE4D-2312-4684-904B-613302AC2B19/MyObject/p2085 ; data: fault)
And when [selectedObject link] is accessed it returns nil. To the best of my knowledge, none of my code is being run following the willEnterForeground and didBecomeActive methods when the app resumes from the background.
The managedObjectContext of a NSManagedObject is not guaranteed. If you need it, you have to also store a strong reference to the context.
If upon resume your data has become stale (you cannot access the stored object's properties), re-fetch in viewWillAppear.
Reading comments in another answer, I see this comment from the OP:
The app delegate isn't returning nil, its returning the same context at the same memory address. Only the [selectedObject managedObjectContext] is returning nil.
If it's true, it means that your app, at some point, is updating its [AppDelegate context] property (probably allocating a new one, for this you should show your AppDelegate code).
The previous context is deallocated (since NSManagedObject doesn't retain its own context), the object points to a non valid / nil reference and for this reason is unable to fire the fault.
The problem turned out to be some of my code was being executed inadvertently. The main view of my app has a MKMapView displayed. For some reason on iOS 8+ when the app resumed from the background, the regionDidChangeAnimated event was being fired even though the region had not changed and this view wasn't even being displayed.
There was code running for regionDidChangeAnimated that reloads the map data from a web service and stores it in core data (hence why my core data was perma-faulted). This was only meant to be fired when the map was changed via user interaction or code I explicitly wrote to change the map region.
My guess is the map is doing some caching and when it resumes from the background it resets the region which is causing the event to fire inadvertently. To work around this, I added a check to ensure the view is currently displayed.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
if (self.navigationController.visibleViewController != self)
{
return;
}
// Update map logic here...
It seems strange to me that there wouldn't be some check in place to prevent this event from firing when retrieving from a cached state, but c'est la vie.

Objective-C: Keeping Variable Alive in Another Class

In my app, when the user launched the app, I create an instance of a class in my AppDelegate and call a method in the class which compares all of the user's iOS contacts to find which ones are using my app, and puts those contacts into an NSMutableArray
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
GetContactClass *contact = [[GetContactClass alloc] init];
[contact getAllContacts];
...
}
GetContactClass.h
#property (retain, nonatomic) NSMutableArray *appContacts;
At the end of the getAllContacts method, I NSLog out appContacts and it works fine.
However, later in the app I try to set an NSMutableArray in a ViewController to equal appContacts, but I get a (null) array.
ViewController.m
self.searchableContacts = [[NSMutableArray alloc] init];
GetContactClass *contact = [[GetContactClass alloc] init];
self.searchableContacts = contact.appContacts;
What am I doing wrong here?
You're creating an entirely new instance which hasn't been asked to collect all contacts, so it hasn't stored them. By the look of the code the instance which has stored them has been destroyed. As you're running this on the main thread you might as well just ask the new instance to get contacts and delete the code for the old one. It's better however to run the contacts collection on a background thread and keep the result till you need it, in a retained instance variable.
If you want use this data in all app you must use a Pattern Design Singleton:
In objective-c:
#implementation Settings
+ (id)sharedInstance {
static Settings *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Other way is change retain to strong but this is a bad idea and put this property in appDelegate and call all times APPDelegate get property.

Store and fetch NSMutableArray in Core Data

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.

Execution_BAD-ACCESS when deleting data

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).

Resources