Core Data and Managed Object Contexts - ios

I'm new with Core Data and I have some questions about how to do the things in the right way. I think I need a temporaryManagedObjectContext to work with temporary entities, while user is editing a new entity. When user tap save that data I want to insert those entities in the persistedManagecObjectContext and then saveContext. What is the best practice to achieve this? Since I don't want to save in the temp context, it is mandatory to use threading?
Thanks for your knowledge!

You need to merge the changes in the temporary ManagedObjectContext (MOC) into the persisted one. This is an example of how I achieved this.
Im using threading (One MOC per thread), but Im pretty sure it should work out fine without threads aswell.
You call this method to save your changes in the tempMOC:
- (void) saveNewEntry {
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
// Subscribe to "NSManagedObjectContextDidSaveNotification"
// ..which is sent when "[tempMOC save];" is called.
[dnc addObserver: self
selector: #selector(mergeChanges:)
name: NSManagedObjectContextDidSaveNotification
object: tempMOC];
// Save changes
[tempMOC save];
// Remove subscribtion
[dnc removeObserver: self
name: NSManagedObjectContextDidSaveNotification
object: tempMOC];
}
...which will fire off a notification to:
- (void) mergeChanges: (NSNotification*) saveNotification {
// If youre using threads, this is necessary:
[self performSelectorOnMainThread: #selector(mergeToMainContext:)
withObject: saveNotification
waitUntilDone: NO];
// ...otherwise, you could do the merge in this method
// [persistedMOC mergeChangesFromContextDidSaveNotification: saveNotification];
}
...which in turn calls:
- (void) mergeToMainContext: (NSNotification*) saveNotification {
[persistedMOC mergeChangesFromContextDidSaveNotification: saveNotification];
}

I have the same issue (editor needs an object, so create... but if cancel, throw away that object). I ended up with a more brute-force approach, though, precisely to avoid all the multiple objet contexts and merging:
I use a "is temp" field in all my objects. When I need to create a "provisional" object I have a method in my data layer that creates an object as normal, but then flips isTemp=true before returning.
Then, in my "object editor" onCancel there is:
if (obj.isTemp) {
[context deleteObject: ... ];
}
onSave is:
if (obj.isTemp) obj.isTemp = NO;
[context saveAsUsual...];
Note: not addressed here there's the issue of not copying "throw away" changes into an existing object until user confirms saving. Otherwise changes would be sitting there like a trojan horse waiting to be applied when some other code saves the shared context.

Related

iOS: Synchronizing access to CoreData

I'm new to CoreData and I'm trying to create a simple application.
Assume I have a function:
func saveEntry(entry: Entry) {
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait {
// find if MOC has entry
// if not => create
// else => update
// saving logic here
}
}
It can introduce a problem: if I call saveEntry from two threads, passing the same entry it will duplicate it. So I've added serial queue to my DB adapter and doing in following manner:
func saveEntry(entry: Entry) {
dispatch_sync(serialDbQueue) { // (1)
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait { // (2)
// find if MOC has entry
// if not => create
// else => update
// saving logic here
}
}
}
And it works fine, until I'd like to add another interface function:
func saveEntries(entries: [Entry]) {
dispatch_sync(serialDbQueue) { // (3)
let moc = NSManagedObjectContext(concurrencyType: .NSPrivateQueueConcurrencyType)
moc.parentContext = savingContext
moc.pefrormBlockAndWait {
entries.forEach { saveEntry($0) }
}
}
}
And now I have deadlock: 1 will be called on serialDbQueue and wait till saving finishes. 2 will be called on private queue and will wait for 3. And 3 is waiting for 1.
So what is correct way to handle synchronizing access? As far as I understand it's not safe to keep one MOC and perform saves on it because of reasons described here: http://saulmora.com/coredata/magicalrecord/2013/09/15/why-contextforcurrentthread-doesn-t-work-in-magicalrecord.html
I would try to implement this with a single NSManagedObjectContext as the control mechanism. Each context maintains a serial operation queue so multiple threads can call performBlock: or performBlockAndWait: without any danger of concurrent access (though you must be cautious of the context's data changing between the time the block is enqueued and when it eventually executes). As long as all work within the context is being done on the correct queue (via performBlock) there's no inherent danger in enqueuing work from multiple threads.
There are of course some complications to consider and I can't offer real suggestions without knowing much more about your app.
What object will be responsible for creating this context and how will it be made available to every object which needs it?
With a shared context it becomes difficult to know when work on that context is "finished" (it's operation queue is empty) if that represents a meaningful state in your app.
With a shared context it is more difficult to abandon changes should you you want to discard unsaved modifications in the event of an error (you'll need to actually revert those changes rather than simply discard the context without saving).

Callback from inside a block (Objective C)

I have this method with a block in it, I want it to send the userID to another method as soon as it exists. userID is a value that is parsed from the internet, so it usually takes about 2 seconds to load up and 'exist'. Is there any way I can do a 'when userID exists, send it to another method?
Here's all my code:
- (void)parseForUserID {
//Get the Data you need to parse for (i.e. user main page returned as a block of NSData.
TClient *client = [[TClient alloc] init];
[client loginToMistarWithPin:#"20014204" password:#"yuiop" success:^{
[client getUserID:^(NSString *result) {
NSString *userIDWithHTML = [self userIDRegex:result];
NSString *userID = [self onlyNumbersRegex:userIDWithHTML];
//if userID exists, send it to another method in a different class
}];
} failure:^{
NSLog(#"login failed from controller");
}];
}
I see that this is the third question you ask related to the same issue, so I guess you're having some trouble understanding blocks.
First you have to understand that the block, in a certain sense, can be seen as a function. The difference is that, unlike a function, the block has no name, and instead of using function's name you just place the code inline where you need it.
Second thing to understand is that a block is usually used as a callback. Other callback mechanisms are function pointers and delegates. When you pass a block as a parameter to a function you're basically telling the function: "Hey, when certain conditions are met, execute this little code for me, please"
Third think to understand is if the block (or any callback) will be called synchronously. Actually this has nothing to do with the block itself, per se, but rather with the function being called. If the function is asynchronous, the function will create another thread and return immediately to execute the next line after the one that invoked the asynchronous function. Meanwhile the new thread will execute some code (the body of the async function) and, eventually execute the block passed as parameter, and finally the thread is killed and doesn't exist any more. (Note: There's no way to know if a function is synchronous or asynchronous other that reading the documentation for such a function).
Now let's go back to your code.
[client loginToMistarWithPin:#"20014204" password:#"yuiop" success:^{
[client getUserID:^(NSString *result) {
NSString *userIDWithHTML = [self userIDRegex:result];
NSString *userID = [self onlyNumbersRegex:userIDWithHTML];
// PLACE HERE THE CODE TO EXECUTE WHEN SUCCESSFULLY LOGGED IN
[anotherClassInstance someMethod:userID];
}];
} failure:^{
NSLog(#"login failed from controller");
}];
Everything that should be executed once the user logged in should be placed inside the block (if the function is synchronous you could place it after the block). To send the userID to another class, just call that class' method as you would in any other part of your code.
In my opinion using a delegate is not necessary (although only you would know, since you're the architect of your app).
As #santhu said, use either the delegate pattern or notification pattern. It's also a common practice to use both of them. Usually a delegate is the correct approach but sometimes you need a notification. Using both covers all your bases.
Look them up before deciding which and for full details on how they work, but basically:
[client getUserID:^(NSString *result) {
NSString *userIDWithHTML = [self userIDRegex:result];
NSString *userID = [self onlyNumbersRegex:userIDWithHTML];
// delegate pattern:
if ([self userIdIsValid:userID]) {
if (self.delegate && [self.delegate respondsToSelector:#selector(foundValidUserID:)]) {
[self.delegate foundValidUserID:userID];
}
} else {
if (self.delegate && [self.delegate respondsToSelector:#selector(foundInvalidUserID:)]) {
[self.delegate foundInvalidUserID:userID];
}
}
// notification pattern:
if ([self userIdIsValid:userID]) {
[[NSNotificationCenter defaultCenter] postNotificationName:MyFoundValidUserIDNotification object:self userInfo:#{#"userID": userID}];
}
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:MyFoundInvalidUserIDNotification object:self userInfo:#{#"userID": userID}];
}
}];
There is a third option, which is you could use a block callback... this is how the new kids on the block do it... there's no well defined pattern here, blocks are brand new and delegates/notifications are 20 years old. But here's how I'd use a block to define a callback:
typedef void (^UserIdCallbackBlock)(NSString *userID);
- (void)parseForUserIDOnSuccess:(UserIdCallbackBlock)successCallback onFailure:(UserIdCallbackBlock)failureCallback {
...
NSString *userID = [self onlyNumbersRegex:userIDWithHTML];
if ([self userIdIsValid:userID]) {
successCallback(userID);
} else {
failureCallback(userID);
}
...
}
I would like to give a hint regarding your comment:
for code readability, it's not that I just have one more task to do, the thing I put inside this block will also have a block and another block and another.
This is a typical asynchronous pattern - called "continuation".
Given, that you should also implement proper error handling and that you should also provide a means to cancel that whole "chain" of asynchronous tasks at any point, the typical solutions with NSOperationQueues and NSOperations, dispatch_queue and blocks, NSNotifications or delegates will inevitable become unduly elaborate, complex and difficult to comprehend by others. (There's already an answer here that demonstrates this grandiose ;) )
So, whenever problems become more complex and the "built-in frameworks" don't provide a comfortable solution, third party libraries come into play to help you.
But first, lets have a non-trivial example, based on your comment:
it's not that I just have one more task to do, the thing I put inside this block will also have a block and another block and another
OK, lets suppose your objective is actually:
Asynchronously perform a Login for a web service.
Then, if that succeeded, asynchronously fetch a list of objects as JSON.
Then, if that succeeded, parse the JSON response.
Then, if that succeeded, insert the objects into a managed object context and asynchronously save the chain of managed object contexts and make it persistent.
When this all above succeeded, update the UI on the main thread
If anything fails, report the error of the task that failed
I will show how a solution utilizing a library implementing "promises" (see wiki Future and promises) may look like:
Without further ado, and without thorough explanation what that "Promise" is about, suppose we have a method defined in your View Controller, which is declared:
- (RXPromise*) loginToMistarWithPin:(NSString*)pin
password:(NSString*)password;
Note: The above method is asynchronous and it is functional equivalent to the form:
typedef void (^completion_t)(id result, NSError*error);
- (void) loginToMistarWithPin:(NSString*)pin
password:(NSString*)password
completion:(completion_t)completion;
then suppose we have another method in your View Controller, fetching objects from a remote server (asynchronous as well):
- (RXPromise*) fetchObjects;
Then, suppose we have a class CoreDataStack which consists of a "root context" saving to the persistent store having a child managed object context, the "main context", which is associated to the main thread.
The class CoreDataStack defines this method, which saves a chain of managed object contexts, which is basically setup: childContext -> main_context -> root_context:
- (RXPromise*) saveWithChildContext:(NSManagedObjectContext*)childContext;
Then, the whole task as stated in the steps 1. through 5. can be expressed as follows:
[client loginToMistarWithPin:#"20014204" password:#"yuiop"]
.then(^id(id result){
// login succeed, ignore result which is #"OK"
// Now fetch the objects with an asynchronous network request,
// returning JSON data as a NSData object when it succeeds:
return [client fetchAllUsers];
}, nil)
.then(^id(NSData* json){
// The network request succeeded, and we obtain the JSON as NSData.
// Parse it and get a Foundation representation:
NSError* error;
id jsonArray = [NSJSONSerialization JSONObjectWithData:json
options:0
error:&error];
if (jsonArray) {
return jsonArray; // handler succeeded
}
else {
return error; // handler failed
}
})
.then(^id(NSArray* objects){
// Parsing succeeded. Parameter objects is an array containing
// NSDictionaries representing a type "object".
// Save into Core Data:
// Create a managed object context, which is a child of the
// "main context" of a Core Data stack:
NSManagedObjectContext* moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = self.coreDataStack.managedObjectContext;
// Create managed objects and initialize them with the given
// NSDictionary:
for (NSDictionary* object in objects) {
// note: `createWithParameters:inManagedObjectContext` executes on
// the context's queue
[Object createWithParameters:object inManagedObjectContext:moc];
}
// Finally, asynchronously save into the persistent store and
// return the result (a RXPromise):
return [self.coreDataStack saveWithChildContext:moc];
}, nil)
.thenOn(dispatch_get_main_queue(), ^id(id result){
// Saving to the backing store succeeded. Now, we possibly want to
// update some UI on the main thread. We are executing on the main
// thread already (see thenOn(dispatch_get_main_queue())
...
[self.tableView reloadData];
return nil;
}, nil)
.then(nil, ^id(NSError* error){
// If something went wrong in any of the above four steps, the error
// will be propagated down and "cought" in this error handler:
NSLog(#"Error: %#", error);
});
Disclaimer: I'm the author of the library RXPromise available at GitHub. There are a few more Objective-C libraries which implement Promises.

NSManagedObject timeStamp update

I want to track changes of NSManagedObject properties, in order to keep NSData *lastUpdate property "up to date"
There are several approaches to get Notified when NSManagedObject changes its properties
I. First is to override the setter Methods of all properties you want to track. Which is quite complicated in NSManaged object - check it here
II. Second could be a good one. You can just override "didChangeValueForKey" method That is called on every property change.
-(void)didChangeValueForKey:(NSString *)key{
[super didChangeValueForKey:key];
NSLog(#"Value for key:%# has changed", key);
}
Unfortunately we should not override this method due to the documentation that says...:
"You must not override this method."
III. Key-value-observing leads us back to IInd approach, with overriding "didChangeValueForKey".
upd.
IV. I tried to override -willSave method
-(void)willSave{
NSArray *observedKeys = #[#"name", #"imageType"];
NSDictionary * changesALL = self.changedValues;
for (id key in changesALL){
if ([observedKeys containsObject:key]){
self.lastUpdate = [NSDate date];
NSLog(#"updated For key: %#", key);
}
}
}
This led infinitive loop, which is described in documentation.
(altho the right approach is described here, so I've answered this question already)
If you want to update a persistent property value, you should typically test for equality >of any new value with the existing value before making a change. If you change property >values using standard accessor methods, Core Data will observe the resultant change >notification and so invoke willSave again before saving the object’s managed object >context. If you continue to modify a value in willSave, willSave will continue to be called >until your program crashes.
For example, if you set a last-modified timestamp, you should check whether either you >previously set it in the same save operation, or that the existing timestamp is not less >than a small delta from the current time. Typically it’s better to calculate the timestamp >once for all the objects being saved (for example, in response to an >NSManagedObjectContextWillSaveNotification).
A suitable solution for your use case to override the willSave method and use it to set the new lastUpdated value. This method is called automatically on dirty objects before they are saved into the context.
If you need to verify what is dirty you can use the contents of the changedValues property.
So after all I figured out that the best solution to track changes of Managed Object is to register for NSManagedObjectContextWillSaveNotification instead, and set the timestamp on all updated and inserted objects in the managed object context. The registered method could look like this:
-(void)contextWillSave:(NSNotification *)notify
{
NSManagedObjectContext *context = [notify object];
NSDate *dateOfTheLastModification = [NSDate date];
for (NSManagedObject *obj in [context insertedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastUpdate"];
}
for (NSManagedObject *obj in [context updatedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastUpdate"];
}
}
This assumes that all your entities have a lastModifiedDate attribute, otherwise you have to check the class of the objects.
To avoid the infinite loop, try this magic:
- (void)willSave{
if(![self.changedValues objectForKey:#"localModificationDate"]){
self.localModificationDate = [NSDate date];
}
else{
[super willSave];
}
}
Once the modification date has been set it won't set it again for the current save. There is a side affect that if the save fails and you save successfully again, I reckon the date will be the from the previous save attempt.
This is fine if you are saving the context after every edit, but the usual design of core data is to only save either at app suspend or after a long time. So it's likely the lastUpdate will be needed for something before then and it won't have the new value yet.

Single model communicates between RestKit and other classes, requests fail when not using singleton

In my project I have a singleton model class, APIModel, that handles all the necessary calls to an API. I am using RestKit and set HTTP headers quite often.
This is my issue:
AModel
- (void)makeRequest {
[APIModel apiObject].getSpecificDataDelegate = self;
[[APIModel apiObject] loadSpecificData];
}
BModel
- (void)makeRequest {
[APIModel apiObject].getSpecificDataDelegate = self; // removes AModel as delegate so it ends up receiving both responses
[[APIModel apiObject] loadSpecificData];
}
AModel sets itself as the delegate then BModel sets itself as the delegate. BModel ends up receiving both API responses.
My solution to solve this was to spin up different instances of APIModel for every class that needed it.
- (void)makeRequest {
self.apiObject.getSpecificDataDelegate = self;
[self.apiObject loadSpecificData];
}
- (APIModel *)apiObject {
if (!_apiObject) apiObject = [[APIModel alloc] init]; // classes own instance
return _apiObject;
}
For some reason though all of these APIModel instances never appropriately attach the HTTP headers to the requests, so they all fail on the API’s end. Any models still using the singleton object still work fine.
I am thinking it is an issue with RKClient’s singleton (sharedClient) but am not sure. It is not nil and I can set the HTTP headers and even print them out, but my API keeps throwing exceptions. Is there any obvious reason why the HTTP headers would fail to attach themselves to the request when not using a singleton? Is there a different or better design pattern I can utilize?
I found this question and although insightful it does not completely relate to my issue, but is there a way to do something similar?
I thought about using NSNotificationCenter but then more information would need to be passed around to allow AModel and BModel to know what data is for them.
Singletons with single delegates = BAD DESIGN.
As you are seeing, you run into ownership problems almost immediately.
NSNotificationCenter allows you to pass as much information as you want with your notification. It's fine to have a singleton that multiple objects can fire off requests with, but you need to keep in mind any one object that interacts with the singleton, will likely not be the only one.
Do something like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(requestSucceeded:) name:RequestSucceededNotification object:nil];
self.someID = #"Some_ID_You_Have";
//Doesn't have to be an ID, but you need a way to differentiate the notifications
[[APIModel apiObject] loadSpecificDataWithID:self.someID];
Then from your APIModel class once you have the data you can do something like this:
[[NSNotificationCenter defaultCenter] postNotificationName:RequestSucceededNotification object:theID userInfo:#{#"data" : data}];
And lastly in both AModel and BModel
- (void)requestSucceeded:(NSNotification *)notification
{
if ([self.someID isEqualToString:[notification object]] == YES) {
//Grab your data and do what you need to with it here
NSData *data = [[notification userInfo] objectForKey:#"data"];
}
}

EXC_BAD_ACCESS when setting a CoreData field (but not other fields) after an async operation

I have a UserService singleton that contains the following method. I am getting an EXC_BAD_ACCESS on the line that sets book.downloaded in the progress callback.
I want the service to continue to download and update the book object even if the user exits the view they are on (currently a detail view for a table). It all works fine if I keep the detail view open, but when I back out to the parent table view, I get the bad access.
My first thought was that book is being released by ARC, but that doesn't hold water. You can set other properties, and even read the current value of book.downloaded, but the minute you try to set it. Boom.
Book is a CoreData class generated by mogenerator, if it matters.
// UserService is a singleton.
// Only errors if I back out of the detail view to the parent table view.
-(void)addBook:(Book *)book downloadProgress:(void (^)(Book *))progressCb downloadComplete:(void (^)(Book *))cb {
book.purchasedValue = YES;
book.downloadedValue = 0.0;
// making my UserService own the book with a strong reference doesn't help
self.strongBookProperty = book;
[FileService.shared
downloadFiles:[FileService.shared byBookId:book.bookId]
progress:^(float percent) {
book.title = #"fake title"; // no error!
NSLog(#"old downloaded %#", book.downloaded); // no error!
book.downloaded = [NSNumber numberWithFloat:percent]; // EXC_BAD_ACCESS
progressCb(book);
}
complete:^() {
book.downloaded = [NSNumber numberWithFloat:1.0];
cb(book);
}
];
}
UPDATE I am key-value observing book.downloaded in the details view that kicks this off. If I comment out the key-value observation it doesn't error. Why? (Obviously this makes my view not update). This explains why only that property was throwing an error (it's the only one being key-value observed).
I was not removing my key-value observers on book in the dealloc method of my details view. The following got rid of the error. (In the details view)
-(void)dealloc {
[self.book removeObserver:self forKeyPath:#"downloaded"];
[self.book removeObserver:self forKeyPath:#"purchased"];
}
I thought ARC would take care of that for me, but I it sort of makes sense that it wouldn't know how. I also don't really understand how that would cause a bad access on the line I was getting it. It seems like it would give a bad access in the dealloced observer instead.

Resources