I have encountered a threading issue I cannot solve. I want to perform a large Core Data save operation of about 12000 objects on a separate thread in a certain class, and in another class control a button action in relation with the save operation being finished. What is the best approach on this?
This is how the save operation looks like:
Class A
-(void) saveAsync
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
//arrayOfObjects holds the 12000 objects
for(aClass *object in arrayOfObjects)
{
[self saveToCoreData: object];
}
NSLog(#"Finished saving");
});
}
-(void) saveToCoreData : (aClass *) object
{
//perform save operation here
}
And this is the action method on my button (which is really nothing yet)
Class B
-(IBAction) buttonActionMethod
{
//take different actions depending on the objects being persisted to the store or still saving
}
I am asking for a solution that would allow me to know if the objects are saved at a button press. The code I provided is just a raw example to express the idea, I don't expect it to work like that. I have thought of using NSOperationQueue or create threads or use groups, but I have not found a solution that works.
Thank you in advance!
You need to store a BOOL on Class A, which will indicate the state of saving,
and check that BOOL from Class B, and make different actions.
>> Class A
#property (atomic) BOOL isSaving;
- (void)saveAsync
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
//arrayOfObjects holds the 12000 objects
self.isSaving = YES;
for(aClass *object in arrayOfObjects) {
[self saveToCoreData:object];
}
self.isSaving = NO;
NSLog(#"Finished saving");
});
}
- (void)saveToCoreData:(aClass *)object
{
//perform save operation here
}
>> Class B
- (IBAction)buttonActionMethod
{
// take different actions depending on the objects being persisted to the store or still saving
if (classA.isSaving) {
} else {
}
}
You can subscribe to NSManagedObjectContextDidSaveNotification:
Posted whenever a managed object context completes a save operation.
The notification object is the managed object context. The userInfo
dictionary contains the following keys: NSInsertedObjectsKey,
NSUpdatedObjectsKey, and NSDeletedObjectsKey.
I strongly recommend NOT to use any class variables, as they store class state which is not a real part of it
Related
I am moving a NSData property out of a CoreData object and into a separate object, so self.pdfData becomes self.pdf.data. Does this look like the right approach to manage creation and deletion of the secondary object?
- (void)setPdfData:(NSData *)pdfData
{
if (!pdfData) {
if (self.pdf) {
[self.managedObjectContext deleteObject:self.pdf];
self.pdf = nil;
}
}
else {
if (!self.pdf) {
self.pdf = [BaseFormPDF insertInManagedObjectContext:self.managedObjectContext];
}
self.pdf.data = pdfData;
}
}
- (NSData *)pdfData
{
return self.pdf.data;
}
Yes, this is a good approach.
1) by moving the data to a separate entity you can fetch the main entity without loading the large data into memory.
2) psudo properties on managedObjects is are really cool and work very well for things like this. But I would be worried about doing too much in a setter. In this case I think it is OK, but doing more can cause issues. If a programmer is just setting thing.pdfData = data and lots of stuff is happening that the programmer didn't expect that could cause bugs.
This question already has answers here:
Run code only after asynchronous function finishes executing
(2 answers)
Closed 7 years ago.
I've been away from iOS and programming for a long time and getting back in to it and I'm lost on how best to handle blocks inside methods that return data back to another class.
I created a method to download some data from the internet.
-(NSArray*) getDataFromWeb
{
DataSync * dataS = [[DataSync alloc] init];
[dataS downloadData:#"Full"
successBlock:^(NSArray *dataArray)
{
NSLog(#"Data : %#", dataArray);
myDataArray = [dataArray copy];
}
errorBlock:^(NSError *error)
{
NSLog(#"ERROR: %#",error);
}];
return myDataArray;
}
Normally in the past I would have had a return statement returning the array. But because the block is off getting data on another thread the return will fire too early and the data wont have been retrieved and I just get a (null) back.
What is the recommended way to pass the data back to another class that is called this method after the block has finished it work and populated the array with data? Can I have two return states in each of the success or error section of the blocks?
There are two options.
No. 1, and I don't recommend this, is to wait for the block to finish and the return the value. Like I said, you should avoid this unless you REALLY know what you are doing, for this reason, I'm not going to go into details on it's implementation.
No. 2 is to not return the value, but to handle it with a completion block. Basically, you make your function return void, and add a block parameter with an object argument. A call to this method would look a lot like the call to downloadData.
Let's say your method now looks like this -(void)getDataFromWebWithCompletion:(CompletionBlock)block, at the end of the block for downloadData, you would do something like this:
if(block){
block(dataArray)
}
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.
I know what #synchronized() does, but...
sometimes we have:
1- #synchronized(self)
2- #synchronized([MyClass class])
3- #synchrinized(myObj)
What is the difference, and what is the parameter I should pass to this block ?
From the documentation:
The object passed to the #synchronized directive is a unique
identifier used to distinguish the protected block. If you execute the
preceding method in two different threads, passing a different object
for the anObj parameter on each thread, each would take its lock and
continue processing without being blocked by the other. If you pass
the same object in both cases, however, one of the threads would
acquire the lock first and the other would block until the first
thread completed the critical section.
So it depends on what you want to protect from being executed simultaneously,
and there are applications for all three cases.
For example, in
-(void)addToMyArray1:(id)obj
{
#synchronized(self) {
[self.myArray1 addObject:obj];
}
}
-(void)addToMyArray2:(id)obj
{
#synchronized(self) {
[self.myArray2 addObject:obj];
}
}
both #synchronized blocks cannot be executed simultaneously by two threads calling
the method on the same instance (self), thus protecting simultaneous access to the
arrays from different threads.
But it also prevents the block from the first method
to be executed simultaneously executed with the block from the second method, because they
use the same lock self. Therefore, for more fine-grained locking, you could use
different locks:
-(void)addToMyArray1:(id)obj
{
#synchronized(self.myArray1) {
[self.myArray1 addObject:obj];
}
}
-(void)addToMyArray2:(id)obj
{
#synchronized(self.myArray2) {
[self.myArray2 addObject:obj];
}
}
Now the simultaneous access to self.myArray1 and self.myArray2 from different threads
is still protected, but independently of each other.
A lock on the class can be used to protect access to a global variable.
This is just a trivial example for demonstration purposes:
static int numberOfInstances = 0;
-(id)init
{
self = [super init];
if (self) {
#synchronized([self class]) {
numberOfInstances++;
}
}
}
#synchronized should have the same object passed each time. So #synchronized(self) would work best.
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.