I've been trying to fix an issue in our NSOperation subclass and I feel it may be related to our manual change notifications for KVO. All the sources I've checked seem to do the following when updating the NSOperation state:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
In contrast, we have been doing it like this:
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
Can anybody tell me why the former seems to be the recommended way of doing this?
It also seems that Apple's KVO docs recommend the first approach as well. Unfortunately they don't explain why.(https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-SW3)/
The reason is that, conceptually, the operation is changing both states together. You want observers to be notified only after the internal state has been updated for both properties, so that, when handling the notification, the observers see consistent state.
If you do:
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
then observers will get the change notification for the isExecuting property during that didChange... call. If they check the operation properties in their handler, they could see that the operation is not executing (isExecuting returns NO) but also not finished (isFinished still returns NO). That doesn't make sense. The observers could do something odd as a result.
I've implemented NSOperation without following the pattern
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
instead by simply doing
self.executing = NO;
self.finished = YES;
.. without problems (never ending operations or the like) when using NSOperationQueues. It seems NSOperationQueue only listens for 'IsFinished' to determine if an NSOperation is truly finished. This other answer explains better.
NSOperation KVO isFinished
Related
This code works, and postSpamListUpdatedNotification is called
- (void) postSpamListUpdatedNotification
{
[NIDPrivateUtils postNotification:kNIDSpamListsUpdated andError:nil];
}
[self performSelector:#selector(postSpamListUpdatedNotification) withObject:nil];
But if I change it to this, then postSpamListUpdateNotification is never called. Why?
[self performSelector:#selector(postSpamListUpdatedNotification) withObject:nil afterDelay:2.0];
You likely don't have a runloop on this thread. performSelector:withObject:afterDelay: requires a runloop, but performSelector: doesn't.
How to identify Nsoperation dynamically.
I am creating a NSoperation subclass
- (id)initWithConnectDevice:(ConnectDevice *)cDevice toPeripheral:(CBPeripheral*)peripheral oPerationIndex:(int) index{
if (self = [super init]) {
operationIndex = index;
executing = NO;
finished = NO;
self.connectDevice = cDevice;
[self.connectDevice setDelegate:self];
self.connectedPeripheral = peripheral;
}
return self;
}
-(BOOL)isConcurrent{
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
-(void) terminateOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)start {
#autoreleasepool {
if (self.isCancelled){
[timer invalidate];
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
-(void)timerFired:(id)sender{
NSLog(#"timerFired");
}
I am scanning for BLE devices. For example I will found 3 devices, 3 buttons will create.
I am calling this class every time when I am clicking on that button.That means, When I click on button, I am connecting to bluetooth device and get the data from that device for every second thats why I am using timer in start method.
Like that I have multiple bluetooth devices, whenever I clicked on button, I want to create multiple instances of Operation Queue class.
Now, I want to identify which data is coming from which thread.
Could you please help me....
This the way I am calling above class from viewcontroller
OperationQueue *queue = [[OperationQueue alloc] initWithConnectDevice:connectDevices toPeripheral:peripheral oPerationIndex:operationIndex];
queue.delegate = self;
[[[AppDelegate app] mainQueue] addOperation:queue];
operationIndex = operationIndex+1;
Each of your operations has two identifying properties already - the connected device and the index. When the timer fires, depending what you want to do with the data, you can use these properties to tell where the data is coming from.
You can either have a delegate property on the operation, where a delegate method is called when the timer fires that takes the device and the received data as parameters, or you the operation could have a block property, which takes a block to be executed whenever data is received - the block would have the device and the received data as parameters.
Assuming you want to update the UI when the data is received, be sure to call the delegate method or execute the block on the main thread.
hi i am using Xcode 5+ and iOS 7+ , and implementing NSOperationQueue. i create a subclass of NSOpeartion and say i have 50 + operation added to NSOperationQueue. isExecuting and isFinished is overridden in NSOperation`s start method -
-(void)start{
// soeme code is here
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_ isFinished = NO;
[self didChangeValueForKey:#"isFinished"];
}
and after completion task i write this code
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_ isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
MaxConcurrentOperationCount is 2. But after completion of 2 operation next (3rd operation) doesn't get execute main method. please give me some clarification about when this issue comes.
Thanks
Upon startup, you should set isExecuting to YES but you should not set isFinished at all, especially not to YES.
When your async operation is done, you need to set isExecuting to NO, not YES, and you need to set isFinished to YES but you are setting isExecuting a 2nd time.
the question is simple : my app control if there is an update every time it starts. If there is an update a popup will be shown with a Yes or No choose. When user tap Yes 4 methods start. These methods download xml file and upload CoreData. This is the code of the alert :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
But there is a problem : when user tap Yes the alert doesn't disappear and remain on screen until all methods are finished. So i try this other code :
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[NSThread detachNewThreadSelector:#selector(startDownloads) toTarget:self withObject:nil];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
-(void)startDownloads {
NSInvocationOperation *opPoi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdatePoi) object:nil];
NSInvocationOperation *opItinerari=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateItinerari) object:nil];
NSInvocationOperation *opArtisti=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateArtisti) object:nil];
NSInvocationOperation *opEventi=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadControlAndUpdateEventi) object:nil];
NSArray *operations=[[NSArray alloc] initWithObjects:opPoi,opItinerari,opArtisti,opEventi, nil];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
[queue addOperations:operations waitUntilFinished:YES];
[queue waitUntilAllOperationsAreFinished];
}
There is a problem even here : i tap start, but the activity viewer doesn't appear. The alert disappear and the thread start and run the 4 methods one after another.
I need the processes run in background, just like happened with my 2nd code, but i need even my showActityViewer method will be run and show the spinner.
Thanks :)
First things first. You don't need to start 4 operations, since you are already in a secondary thread and do not need that the 4 operations be executed in parallel. You could simply do:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[pool release];
}
Above all, you need define an autorelease pool in startDownloads if you use autorelease in the downloadControl* methods, otherwise I suspect you will have leaks.
As to why the activity indicator does not show up, it depends on the fact that you are calling:
[self hideActivityViewer];
immediately after detach. So, you are showing it and removing it, before event the UI has got the time to update itself. Remove that line from there and rewrite startDownloads like this:
-(void)startDownloads {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self performSelectorOnMainThread:#selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
[pool release];
}
Here notice that I am calling into the main thread to hideActivityViewer because only the main thread can safely use UIKit.
EDIT:
I did not know that you were using Core Data in the download methods...
Have a look at Concurrency with Core Data. You will need to tweak a bit your code by at least using a separate managed object context for your secondary thread (I don't know if it is feasible for you to create the moc there).
Also have a look at this tutorial from Cocoa is my Girlfriend.
As an alternative to all that, you could consider doing:
if (buttonIndex==1) {
[self showActivityViewer];
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
with:
-(void)startDownloads {
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
}
This does not use threads at all, but I am not sure that the activity viewer will show without any glitch. One more level of hack, if needed, and you could specify a delay in
[self performSelector:#selector(startDownloads) withObject:nil afterDelay:0.1];
i've an app that when start control updates and other things. If the app find some updates they will ask user if this updates have to be done. If user select YES i want that a spinner appear on main screen until update finish. But when i tap YES my alert view doesn't disappear and remain on screen until update is finished.
Is it possible to create a thread that run on the main thread and stop when update in finished?
Thanks
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex==1) {
[self showActivityViewer];
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
[self hideActivityViewer];
NSLog(#"AGGIORNA");
} else {
NSLog(#"NON AGGIORNARE");
return;
}
}
If the methods
[self downloadControlAndUpdatePoi];
[self downloadControlAndUpdateItinerari];
[self downloadControlAndUpdateArtisti];
[self downloadControlAndUpdateEventi];
are executed synchronously (that means that they return only after having processed completely), so:
[self hideActivityViewer];
is executed only at the very end.
A simple approach to this is scheduling the execution of your methods on the main thread:
[self performSelector:#selector(downloadControlAndUpdatePoi) withObject:nil afterDelay:0];
....
[self hideActivityViewer];
so that those methods are executed only after control has returned to the main loop and the UI has been updated.
Otherwise, you could use:
+ detachNewThreadSelector:toTarget:withObject:
from NSThread, to do more or less the same. In this case I would suggest creating a wrapper method for all of your dowloadAndUpdate... methods, but keep in mind that you can't update the UI from a secondary thread.
In both cases, you should take some care about synchronizing the download... operations with the rest of your workflow after removing the alert view.