I created an IBAction that I link to saveButton, when I call all core data methods inside the IBAction it works fine in following code:
- (IBAction)saveButtonTap:(id)sender{
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *object = [NSEntityDescription insertNewObjectFor:#"Person" inManagedContext:context];
[self setValue:firstNameTextField.text forKey:#"firstName"];
[self setValue:lastNameTextField.text forKey:#"lastName"];
NSError *error = nil;
if (![context save:&error])
NSLog(#"Can't save transaction - %# || %# ", error, [error localizedDescription]);
}
And when I separate the core data saving methods as the below, the saved firstName and lastName, showing null when fetched. It didn't return error, but I have no idea after googling all day. Could someone point anything wrong with my code?
Person.h + Person.m
#interface Person : NSManagedObject
#property(strong) NSString *firstName;
#property(strong) NSString *lastName;
- (void)save;
#end
#implementation Person
#synthesize firstName;
#synthesize lastName;
- (void)save{
[self setValue:self.firstName forKey:#"firstName"];
[self setValue:self.lastName forKey:#"lastName"];
}
#end
mainViewController.h + mainViewController.m
#interface mainViewController : UIViewController
#property(strong) ManagedObjectContext *context;
#property(strong) Person *person;
- (IBAction)saveButtonTap:(id)sender;
#end
#implementation mainViewController
#synthesize context;
#synthesize person;
- (void)viewDidLoad{
// ... some view did Load rituals
if(context == nil){
context = [self managedObjectContext]; // Assume this method calls for managed object context from shared application.
}
if(person == nil){
person = [NSEntityDescription insertNewObjectFor:#"Person" inManagedContext:context];
}
}
- (IBAction)saveButtonTap:(id)sender{
person.firstName = firstNameTextField.text;
person.lastName = lastNameTextField.text;
[self.person save];
NSError *error = nil;
if (![context save:&error])
NSLog(#"Can't save transaction - %# || %# ", error, [error localizedDescription]);
}
#end
There is no need of save function for Person.
person.firstName = firstNameTextField.text
is equivalent to
[self setValue:self.firstName forKey:#"firstName"];
What you need, is just this:
(IBAction)saveButtonTap:(id)sender{
person.firstName = firstNameTextField.text;
person.lastName = lastNameTextField.text;
NSError *error = nil;
if (![context save:&error])
NSLog(#"Can't save transaction - %# || %# ", error, [error localizedDescription]);
}
But, I am not sure whether this is the cause of the issue. Try it and let me know.
Related
Just started programming on objective-c and now i have issue with which can't deal by myself. I'm receiving data from asynchronous request and try to delver it to singleton, but it's not changed.
This is where i'm trying to store my data
Data.h
#import <Foundation/Foundation.h>
#interface Data : NSObject
#property (nonatomic, strong) NSDictionary *products;
-(void)setProducts:(NSDictionary *)value;
#end
Data.m
#import "Data.h"
#implementation Data
+(Data *)sharedInstance
{
static Data *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[Data alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if ( self )
{
_products = [[NSDictionary alloc] init];
}
return self;
}
#end
This is the class, where i'm receiving data from server:
ConnectionService.m
- (void)getProductsWithCompletion:(void (^)(NSDictionary *products))completion
{
NSString *urlString = [NSString stringWithFormat:#"serverurl", [[AppDelegate instance]getUrl]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *getData = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSString *rawJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *value = [rawJson JSONValue];
completion(value);
}];
[getData resume];
}
This is the class where i'm calling request and try to deliver it to singleton:
viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance] products]);//all is working, products contains data
}];
// checking received data
NSDictionary *tmp = [[Data sharedInstance] products];
NSLog(#"tmp: %#", tmp); //now it's null
}
The issue is the fact that the request is asynchronous and things aren't happening in the order you expect:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
// (2)
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);//all is working, products contains data
}];
// (1)
NSDictionary *tmp = [[Data sharedInstance]products];
NSLog(#"tmp: %#", tmp); //now it's null
}
In the code you posted, (1) will happen before (2). That's because (2) is part of the completion block and is set to run once the network request has completed and all the data has been parsed and is ready to use. While that asynchronous request is prepared and run in a background thread, the main thread ((1)) continues and executes before the request has taken place.
To resolve the issue, move your logging into the completion routine, or simply remove (1).
Another way is to use protocol, to notify your completion block is finished.So that you can simply do:
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
if(self.delegate){
[self.delegate myNotifyMethod:products];
}
}];
and your protocol method:
-(void)myNotifyMethod:(NSDictionary *)items{
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);
}
You can declare the protocol as:
#protocol MyProtocol <NSObject>
- (void)myNotifyMethod: (NSDictionary *)items;
#end
and set the delegate property as:
#property (nonatomic, weak) id<MyProtocol> delegate;
This is .h file as I have declared
#protocol CoreData <NSObject>
-(NSManagedObjectContext *)managedObjectContext;
#end
#interface coreData : NSObject
{
id<CoreData> delegate;
}
#property (retain)id delegate;
#property (readonly,strong ,nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly,strong ,nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly,strong ,nonatomic) NSPersistentStoreCoordinator *persistStoreCoordinator;
-(void)saveContext;
-(NSURL *)applicationDocumentsDirectory;
#end
This is .m file
#implementation coreData
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistStoreCoordinator = _persistStoreCoordinator;
-(void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if(managedObjectContext !=nil)
{
if([managedObjectContext hasChanges] && ![managedObjectContext save:&error]){
NSLog(#"Unresolved error %#,%#",error,[error userInfo]);
abort();
}
}
}
#pragma mark -core data stack
-(NSManagedObjectContext *)managedObjectContext
{
if(_managedObjectContext !=nil)
{
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator =[self persistStoreCoordinator];
if (coordinator !=nil)
{
_managedObjectContext=[[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
-(NSManagedObjectModel *)managedObjectModel
{
if(_managedObjectModel !=nil)
{
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Superallocation" withExtension:#"momd"];
_managedObjectModel =[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
-(NSPersistentStoreCoordinator *)persistStoreCoordinator
{
if(_persistStoreCoordinator !=nil)
{
return _persistStoreCoordinator;
}
NSURL *storeURL =[[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"SuperantZ.sqlite"];
NSError *error = nil;
_persistStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if([_persistStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"unresolved error %#,%#",error,[error userInfo]);
abort();
}
return _persistStoreCoordinator;
}
-(NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Questions:
How to declare core data in view controller not in AppDelegate class?
While using this code in AppDelegate its working why?
After I place in the normal class it is giving me error
like=[AppDelegate managedObjectContext]
2.while using this code in AppDelegate its working why?
3.After i place in the normal class its giving me error like=[AppDelegate managedObjectContext]
There's your answer - it still thinks the code is in the app delegate.
This
[AppDelegate managedObjectContext]
should be
[coreData managedObjectContext]
And everything needs to be wired up properly for this to work.
Incidentally, calling a class 'coreData' is pretty horrible. I'll forget about the missing capital letter (Obj-C classes traditionally start with a capital to reinforce that they are classes), the reason I point it out is you are using the name of something that already exists (bar one letter) and could become insanely confusing down the line.
2 suggestions
Rename the class to something sensible.
Make the class a singleton that can be called from anywhere.
I populate and save: an initial NSManagedObjectContext
setup an NSFetchedResultsController with a different NSManagedObjectContext, which filters on a boolean "show" attribute.
Finally update "show" on yet another NSManagedObjectContext and save:.
I expect that this should cause my NSFetchedResultsController to call NSFetchedResultsControllerDelegate's controllerDidChangeContent:. I never get that call. NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext's accepted answer indicates that in addition to controllerDidChangeContent:, I should get an NSManagedObjectContextObjectsDidChangeNotification, but I don't receive that either.
A complete code example is included below and on github. I've filed a radar with Apple.
#interface HJBFoo : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *show;
#end
#interface HJBAppDelegate () <NSFetchedResultsControllerDelegate>
#property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, strong) NSManagedObjectContext *initialManagedObjectContext;
#property (nonatomic, strong) NSManagedObjectContext *fetchedResultsControllerManagedObjectContext;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#end
#implementation HJBAppDelegate
#pragma mark - UIApplicationDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [UIViewController new];
NSAttributeDescription *nameAttributeDescription = [NSAttributeDescription new];
[nameAttributeDescription setAttributeType:NSStringAttributeType];
[nameAttributeDescription setIndexed:NO];
[nameAttributeDescription setOptional:NO];
[nameAttributeDescription setName:#"name"];
NSAttributeDescription *showAttributeDescription = [NSAttributeDescription new];
[showAttributeDescription setAttributeType:NSBooleanAttributeType];
[showAttributeDescription setIndexed:YES];
[showAttributeDescription setOptional:NO];
[showAttributeDescription setName:#"show"];
NSEntityDescription *fooEntityDescription = [NSEntityDescription new];
[fooEntityDescription setManagedObjectClassName:#"HJBFoo"];
[fooEntityDescription setName:#"HJBFoo"];
[fooEntityDescription setProperties:#[
nameAttributeDescription,
showAttributeDescription,
]];
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel new];
[managedObjectModel setEntities:#[
fooEntityDescription,
]];
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;
if ([self.persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:nil
options:nil
error:&error]) {
self.initialManagedObjectContext = [NSManagedObjectContext new];
[self.initialManagedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
HJBFoo *foo1 = [NSEntityDescription insertNewObjectForEntityForName:#"HJBFoo"
inManagedObjectContext:self.initialManagedObjectContext];
foo1.name = #"1";
foo1.show = #YES;
HJBFoo *foo2 = [NSEntityDescription insertNewObjectForEntityForName:#"HJBFoo"
inManagedObjectContext:self.initialManagedObjectContext];
foo2.name = #"2";
foo2.show = #NO;
error = nil;
if ([self.initialManagedObjectContext save:&error]) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"HJBFoo"];
[fetchRequest setReturnsObjectsAsFaults:NO];
error = nil;
NSArray *initialFoos = [self.initialManagedObjectContext executeFetchRequest:fetchRequest
error:&error];
if (initialFoos) {
NSLog(#"Initial: %#", initialFoos);
self.fetchedResultsControllerManagedObjectContext = [NSManagedObjectContext new];
[self.fetchedResultsControllerManagedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *shownFetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"HJBFoo"];
[shownFetchRequest setPredicate:[NSPredicate predicateWithFormat:#"show == YES"]];
[shownFetchRequest setSortDescriptors:#[
[NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES],
]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:shownFetchRequest
managedObjectContext:self.fetchedResultsControllerManagedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController.delegate = self;
error = nil;
if ([self.fetchedResultsController performFetch:&error]) {
NSLog(#"Initial fetchedObjects: %#", [self.fetchedResultsController fetchedObjects]);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContext2ObjectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:self.fetchedResultsControllerManagedObjectContext];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^(void){
NSManagedObjectContext *managedObjectContext3 = [NSManagedObjectContext new];
[managedObjectContext3 setPersistentStoreCoordinator:self.persistentStoreCoordinator];
NSFetchRequest *foo2FetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"HJBFoo"];
[foo2FetchRequest setFetchLimit:1];
[foo2FetchRequest setPredicate:[NSPredicate predicateWithFormat:#"name == %#",
#"2"]];
NSError *editingError = nil;
NSArray *editingFoos = [managedObjectContext3 executeFetchRequest:foo2FetchRequest
error:&editingError];
if (editingFoos) {
HJBFoo *editingFoo2 = [editingFoos objectAtIndex:0];
editingFoo2.show = #YES;
editingError = nil;
if ([managedObjectContext3 save:&editingError]) {
NSLog(#"Save succeeded. Expected (in order) managedObjectContextDidSave, controllerDidChangeContent, managedObjectContext2ObjectsDidChange");
} else {
NSLog(#"Editing save failed: %# %#", [error localizedDescription], [error userInfo]);
}
} else {
NSLog(#"Editing fetch failed: %# %#", [error localizedDescription], [error userInfo]);
}
});
} else {
NSLog(#"Failed initial fetch: %# %#", [error localizedDescription], [error userInfo]);
}
} else {
NSLog(#"Failed to performFetch: %# %#", [error localizedDescription], [error userInfo]);
}
} else {
NSLog(#"Failed to save initial state: %# %#", [error localizedDescription], [error userInfo]);
}
} else {
NSLog(#"Failed to add persistent store: %# %#", [error localizedDescription], [error userInfo]);
}
[self.window makeKeyAndVisible];
return YES;
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"controllerDidChangeContent: %#",
[self.fetchedResultsController fetchedObjects]);
}
#pragma mark - notifications
- (void)managedObjectContextDidSave:(NSNotification *)notification {
NSManagedObjectContext *managedObjectContext = [notification object];
if (([managedObjectContext persistentStoreCoordinator] == self.persistentStoreCoordinator) &&
(managedObjectContext != self.fetchedResultsControllerManagedObjectContext)) {
NSLog(#"managedObjectContextDidSave: %#", notification);
[self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
}
- (void)managedObjectContext2ObjectsDidChange:(NSNotification *)notification {
NSLog(#"managedObjectContext2ObjectsDidChange: %#", notification);
}
#end
#implementation HJBFoo
#dynamic name;
#dynamic show;
#end
It seems to me that applying the fix/workaround from NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext solves your problem as well. Your managedObjectContextDidSave method would then look like this:
- (void)managedObjectContextDidSave:(NSNotification *)notification {
NSManagedObjectContext *managedObjectContext = [notification object];
if (([managedObjectContext persistentStoreCoordinator] == self.persistentStoreCoordinator) &&
(managedObjectContext != self.fetchedResultsControllerManagedObjectContext)) {
NSLog(#"managedObjectContextDidSave: %#", notification);
// Fix/workaround from https://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different/3927811#3927811
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[self.fetchedResultsControllerManagedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
}
and the NSLog output looks as expected:
Initial: (
"<HJBFoo: 0xaa19670> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n name = 1;\n show = 1;\n})",
"<HJBFoo: 0xaa1a200> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n name = 2;\n show = 0;\n})"
)
Initial fetchedObjects: (
"<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: <fault>)"
)
managedObjectContextDidSave: NSConcreteNotification 0xaa1f480 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0xaa1ed90>; userInfo = {
inserted = "{(\n)}";
updated = "{(\n <HJBFoo: 0xaa1f2d0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n name = 2;\n show = 1;\n})\n)}";
}}
Save succeeded. Expected (in order) managedObjectContextDidSave, controllerDidChangeContent, managedObjectContext2ObjectsDidChange
controllerDidChangeContent: (
"<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n name = 1;\n show = 1;\n})",
"<HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n name = 2;\n show = 1;\n})"
)
managedObjectContext2ObjectsDidChange: NSConcreteNotification 0xaa1fbb0 {name = NSObjectsChangedInManagingContextNotification; object = <NSManagedObjectContext: 0x745fa50>; userInfo = {
managedObjectContext = "<NSManagedObjectContext: 0x745fa50>";
refreshed = "{(\n <HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n name = 2;\n show = 1;\n})\n)}";
}}
So the following things happen:
Changes are made in the "background" context managedObjectContext3 and saved.
You receive a NSManagedObjectContextDidSaveNotification and managedObjectContextDidSave: is called. This notification contains the object that was updated in the background context.
Calling
[self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
now would not do anything, because the updated object has not been loaded into the fetchedResultsControllerManagedObjectContext or is a fault. In particular, this context would not post a NSManagedObjectContextObjectsDidChangeNotification and the fetched results controller would not update.
But calling willAccessValueForKey for the updated objects first forces the fetchedResultsControllerManagedObjectContext to load these objects and fire a fault.
Calling
[self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
after that actually updates the objects in the fetchedResultsControllerManagedObjectContext.
Therefore a NSManagedObjectContextObjectsDidChangeNotification is posted and the fetched results controller calls the corresponding delegate functions.
Im pretty new to Core Data programming and Cocoa in general, so no wonder I'm having troubles :)
So here is my managedObjectModel method:
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil)
{
return managedObjectModel;
}
NSString *modelPath = [[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"];
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
NSAssert(modelURL != nil,#"modelURL == nil");
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel;
}
Here is the part of the code that crashes:
NSManagedObjectModel *mom = [self managedObjectModel];
managedObjectModel = mom;
if (applicationLogDirectory() == nil)
{
NSLog(#"Could not find application logs directory\nExiting...");
exit(1);
}
NSManagedObjectContext *moc = [self managedObjectContext];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSEntityDescription *newShotEntity = [[mom entitiesByName] objectForKey:#"Entity"];
Entity *shEnt = [[Entity alloc] initWithEntity:newShotEntity insertIntoManagedObjectContext:moc];
shEnt.pid = [processInfo processIdentifier]; // EXC_BAD_ACCESS (code=1, address=0x28ae) here !!!
NSError *error;
if (![moc save: &error])
{
NSLog(#"Error while saving\n%#",
([error localizedDescription] != nil) ? [error localizedDescription] : #"Unknown Error");
exit(1);
}
Im really confused why I'm having this error, since when I hardcoded the Data Model instead of using .xcdatamodeld file it was working just fine!
Any kind of help is really appreciated!
EDIT 1: since I'm having all those questions asked I want to make everything clear, sorry for not providing all this before.
// Entity.h
#import <CoreData/CoreData.h>
#interface Entity : NSManagedObject
#property (strong) NSDate *date;
#property (assign) NSInteger pid;
#end
//Entity.m
#import "Entity.h"
#interface Entity ()
#property (strong) NSDate *primitiveDate;
#end
#implementation Entity
#dynamic date,primitiveDate,pid;
- (void) awakeFromInsert
{
[super awakeFromInsert];
self.primitiveDate = [NSDate date];
}
- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:#"pid"]) {
self.pid = 0;
}
else {
[super setNilValueForKey:key];
}
}
#end
Using scalar values in core data is a bit more work than using the recommended NSNumber. This is described in detail in this section of the Core Data Programming Guide.
I strongly recommend you switch this property to NSNumber. Your assignment statement would then be:
shEnt.pid = [NSNumber numberWithInt:[processInfo processIdentifier]];
I'm developing an iOS app using Core Data. And I have a Log entity with one-to-many relationships with Audio, Photo entities, and one-to-one relationship with Status entity. The log also has text, longitude, latitude properties. I can create the log, change its properties, add status entity, these changes would display right, until I quit the App. All the changes would disappear, and I was looking at the sqlite database, all these changes were never persisted in the database. In the database, the status object will just be created, but not linked to the log object.
But if I add an audio or photo object into the log.audioSet or log.photoSet, the changes I made to log, including the changes to text or status, will suddenly be saved into the database.
So it seems the changes are only maintained in the NSManagedObjectContext, until a related one_to_many entity is added and the [[LTLogStore sharedStore] saveChanges] will suddenly start to work.
I am using a singleton to manage the NSManagedObjectContext. Any ideas?
I would post some code if it's relevant. Thanks.
UPDATE: I'm not sure these code is enough. But basically everything works, and displays, it just doesn't save to the database. I'm using the mogenerator to set the text and latitude, but since everything is in the context. I am not sure this is the code you might need.
CODE:
#interface LTLogStore : NSObject{
}
+ (LTLogStore *)sharedStore;
- (void)removeItem:(Log *)p;
- (Log *)createItem;
- (BOOL)saveChanges;
#property(nonatomic, strong) NSFetchedResultsController *resultsController;
#property(nonatomic, strong) NSManagedObjectModel *model;
#property(nonatomic, strong) NSManagedObjectContext *context;
#end
#implementation LTLogStore
#synthesize resultsController;
#synthesize context, model;
+ (LTLogStore *)sharedStore
{
static LTLogStore *sharedStore = nil;
if(!sharedStore){
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
- (id)init
{
self = [super init];
if(self) {
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc =
[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// Where does the SQLite file go?
NSString *path = [self itemArchivePath];
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error]) {
[NSException raise:#"Open failed"
format:#"Reason: %#", [error localizedDescription]];
}
// Create the managed object context
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
// The managed object context can manage undo, but we don't need it
[context setUndoManager:nil];
}
return self;
}
- (NSFetchedResultsController *)resultsController {
if (resultsController !=nil) {
return resultsController;
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *e = [[model entitiesByName] objectForKey:#"Log"];
[request setEntity:e];
NSSortDescriptor *sd = [NSSortDescriptor
sortDescriptorWithKey:#"created_at"
ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sd]];
[request setReturnsObjectsAsFaults:NO];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if (!success) {
//handle the error
}
return fetchedResultsController;
}
- (NSString *)itemArchivePath
{
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get one and only document directory from that list
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
NSString *storePath = [documentDirectory stringByAppendingPathComponent:#"store.data"];
return storePath;
}
- (BOOL)saveChanges
{
NSError *err = nil;
BOOL successful = [context save:&err];
NSLog(#"Saving changes to the database");
if (!successful) {
NSLog(#"Error saving: %#", [err localizedDescription]);
}
return successful;
}
- (void)removeItem:(Log *)l
{
[context deleteObject:l];
[self saveChanges];
}
- (Log *)createItem
{
Log *p = [NSEntityDescription insertNewObjectForEntityForName:#"Log"
inManagedObjectContext:context];
[self saveChanges];
return p;
}
#end
#interface Log : _Log {
}
//these two are some custom convenience methods for location attributes, but it does the work of setting the longitude and latitude value in the log object, but calling the [[LTLogStore sharedStore] saveChanges] still won't save it into the database.
-(CLLocation*)location;
-(void)setLocation:(CLLocation*)location;
//this all works
-(Audio*)newAudio;
-(Audio*)newAudioWithPath:(NSString*)audioPath;
//after calling this method, even the log.text changes will be saved to the database.
-(void)addAudioWithPath:(NSString*)audioPath;
-(void)removeAudio:(Audio*)audio;
#end
#import "Log.h"
#import "Audio.h"
#import "LTLogStore.h"
#implementation Log
-(CLLocation*)location{
if (!self.longitude || !self.latitude) {
return nil;
}
CLLocation *l = [[CLLocation alloc] initWithLatitude:[self.latitude doubleValue] longitude:[self.longitude doubleValue]];
return l;
}
-(void)setLocation:(CLLocation*)location{
if (location==nil) {
self.latitude = nil;
self.longitude = nil;
}
self.latitude = [NSNumber numberWithDouble: location.coordinate.latitude];
self.longitude = [NSNumber numberWithDouble:location.coordinate.longitude];
[[LTLogStore sharedStore] saveChanges];
}
-(Audio*)newAudio{
Audio *a = [Audio new];
a.log = self;
return a;
}
-(Audio*)newAudioWithPath:(NSString*)audioPath{
Audio *new = [self newAudio];
[new setKey:audioPath];
return new;
}
-(void)addAudioWithPath:(NSString*)audioPath{
Audio *new = [self newAudio];
[new setKey:audioPath];
[[LTLogStore sharedStore] saveChanges];
}
-(void)removeAudio:(Audio*)audio{
[self.audiosSet removeObject:audio];
[[[LTLogStore sharedStore] context] deleteObject:audio];
[[LTLogStore sharedStore] saveChanges];
}
#end
UPDATE:
Problem solved, see answer.
UPDATE QUESTION: Why is my overriding causing the problem? Can someone explain the cause behind the magic of Core Data or maybe KVO behind scene?
Problem solved, I overrode the willChangeValueForKey method in the Log class, which caused the problem, I thought the code is irrelevant. But it IS:
- (void)willChangeValueForKey:(NSString *)key{
//I added the following line to fix my problem
[super willChangeValueForKey:key];
//this is the original line, I want to have this
//because I want to have a isBlank property
//so I can see if the user modified the log
_isBlank = false;
//I tried to also add the following line to be safe.
//turns out this line is not needed, and it will make the problem occur again
//[super didChangeValueForKey:key];
}