Current status
I have created a custom NSOperation object and I want to update some data when it is cancelled.
I've followed what said in this answer and I didn't override the cancel method.
Here is my header:
// MyOperation.h
#interface MyOperation : NSOperation {
}
#property (nonatomic, retain) OtherDataClass *dataClass;
#end
And the implementation
// MyOperation.m
#implementation MyOperation
#synthesize dataClass;
- (void)main {
if ([self isCancelled]) {
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
// Do some work here
NSLog(#"Working... working....")
[self.dataClass setStatusFinished];
NSLog(#"Operation finished");
}
#end
The question
I have several operations in a queue. I was expecting that, when I call cancelAllOperations in the queue, I'll get the "Operation cancelled" text in the log and the status updated in my other class but it is not working. The main method is not being called for the operations in the queue.
Why is this happening and how can I solve it?
Notes
I've tried to overwrite the cancel method with this:
- (void)cancel {
[super cancel];
[self.dataClass setStatusCanceled];
NSLog(#"Operation cancelled");
}
It is working but I've read that this method should not be overridden.
When you call cancelAllOperations, the operations which are already started will have isCancelled set to YES. operations which aren't already started won't start.
Related
How can I do things when two asynchronous requests are finished ?
perform the asynchronous initRequest and asynchronous refreshHeader, they finished after I perform the requestEnd method.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initRequest];
[self requestEnd];
}
- (void)initRequest{
[GBCompanyRequest requestWithSuccess:^(EvaluationAverage *evaluationAverage) {
} failure:^(NSString *message) {
}];
[self refreshHeader];
}
- (void)refreshHeader{
[GBCompanyEvaluationRequest requestWithSuccess:^(EvaluationAverage *evaluationAverage) {
} failure:^(NSString *message) {
}];
}
- (void)requestEnd{
NSLog(#"How can I do things when two asynchronous requests are finished");
}
#end
My suggestion is use dispatch_group_t
dispatch_group_t : Generally use for call/execute group of synchronously or asynchronously method/block and it will notify you after all the method/block execution are done.
For more information about how to use, you can read this Q/A.
Waiting until two async blocks are executed before starting another block
I have a watchkit app that calls a viewcontroller on an iphone app. I have a delegate for a network connection. I'm trying to use a block so that I don't tightly couple my AppDelegate and my view controller too closely. How can I notify my block when the delegate is finished?
ViewController.m
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
completion(YES);
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
}
-(void)setUpAppForWatch{
[network call];
}
AppDelegate.m
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)
(NSDictionary *))reply{
[vc getWatchDataWithCompletion:^(BOOL gotData){
if (gotData){
//I'm done reply dictionary
reply(#{#"data":serlizedData})
}];
add new property in viewcontroller:
#property (nonatomic, strong) void(^completion)(BOOL gotData);
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
self.completion = completion;
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
if (self.completion){
self.completion(YES);
}
}
There're three possible ways.
;tldr - refer to the third one. Else - read everything, it might be useful.
First one
Use private serial queue for performing tasks of finished... method and your block. It will suffice you in case, if finished... always called before block. If not - take a look at the Second one
Use private #property dispatch_queue_t privateSerialQueue; of View Controller.
privateSerialQueue = dispatch_queue_create("PrivateQueue", DISPATCH_QUEUE_SERIAL);
Than, use it like this
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
dispatch_async(privateSerialQueue, ^(){
completion(YES);
});
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
dispatch_sync(privateSerialQueue, ^(void){
//Here goes whatever you need to do in this method before block start
});
//the delegate is finish tell the block completion is done.
}
Second one
Take a look at dispatch_semaphore_t. Make it a public property of your View Controler
#property (readonly) dispatch_semaphore_t semaphore
Create it with starting value 0. It will let you wait in case your block runs before delegate finished... method and run immediately, if finished has already completed before block. Like this
self.semaphore = dispatch_semaphore_create(0);
Then you can use it this way
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
dispatch_semaphore_signal(self.semaphore);
}
[vc getWatchDataWithCompletion:^(BOOL gotData){
if (gotData){
//I'm done reply dictionary
dispatch_semaphore_wait(vc.semaphore, DISPATCH_TIME_FOREVER);
reply(#{#"data":serlizedData})
}];
Third one
Came to my mind while writing the two above =)
Some kind of combination of previous two
Use private property of your view controller
#property (readonly) dispatch_semaphore_t semaphore
Initialize it the same way, as in the second (with starting value 0)
self.semaphore = dispatch_semaphore_create(0);
And use it privately like this
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
completion(YES);
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
dispatch_semaphore_signal(self.semaphore);
}
P. S. Hope, it helps you to get to the point. Feel free to ask anything not clear
I have made a subclass of NSOperation called ˚ to achieve multiple movie downloads . In the appDelegate.m , I have made an object of NSOperationQueue .
- (void)applicationDidFinishLaunching:(UIApplication *)application {
queue = [[NSOperationQueue alloc] init];
[queue setMaximumConcurrentOperationCount:5]
}
MovieDownloadOperation depends on a class called Downloader which actually downloads the movie
and gives callback movieCompletelyDownloadedWithUrl: .
Then , I have made a property called downloadState in MovieDownloadOperation . It has different values like "STARTED" , "DOWNLOADING" , "COMPLETED" , "ERROR".
MyDownloadOperation looks like
-(id)initWithUrl:(NSURL *)url
{
if (self = [super init])
{
_downloader = [[Downloader alloc] initWithUrl:url];
_downloadState = #"STARTED" ;
}
}
-(void)main
{
while(1)
{
if ([_downloadState isEqualToString:#"COMPLETED"])
{
NSLog(#"movie downloaded successfully");
break ;
}
}
}
-(void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
_downloadState = #"COMPLETED" ;
}
This works well for one movie , but when I try to download more than one movie , the UI freezes until the first is downloaded . I think the the problem is the while loop inside the main method , is there a better way to check if the _downloadState is changed to "COMPLETED" ??
It's unclear why the UI freezes with multiple operations, but not with only one download. But, your code sample provokes a couple of thoughts:
Concurrent Operation:
Rather than having a while loop in main, and you'd generally would define your operation to be concurrent (i.e. return YES from isConcurrent). Then movieCompletelyDownloadedWithUrl would post the isFinished event, which would trigger the completion of the operation.
In terms of how to make a concurrent operation, you might define properties for executing and finished:
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
You'd probably want to have a strong property for the URL and the downloader:
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) Downloader *downloader;
And then you might have the following code in the operation subclass:
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (id)initWithUrl:(NSURL *)url
{
self = [self init];
if (self) {
// Note, do not start downloader here, but just save URL so that
// when the operation starts, you have access to the URL.
_url = url;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)main
{
// start the download here
self.downloader = [[Downloader alloc] initWithUrl:self.url];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
// you haven't shown how this is called, but I'm assuming you'll fix the downloader
// to call this instance method when it's done
- (void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
[self completeOperation];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
So, with these methods, you might then have movieCompletelyDownloadedWithUrl call completeOperation like above, which will ensure that isExecuting and isFinished notifications get posted. You'd also want to respond to cancellation event, too, making sure to cancel the download if the operation is canceled.
See Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide for more details.
Don't initiate download until main:
I don't see your main method initiating the download. That makes me nervous that your Downloader initialization method, initWithURL, might be initiating the download, which would be bad. You don't want downloads initiating when you create the operation, but rather you shouldn't do that until the operation starts (e.g. start or main). So, in my above example, I only have initWithURL save the URL, and then main is what starts the download.
Using NSURLConnectionDataDelegate methods in NSOperation:
As an aside, you didn't share how your operation is doing the network request. If you're using NSURLConnectionDataDelegate methods, when you get rid of that while loop in main, you might have problems if you don't schedule the NSURLConnection in a particular run loop. For example, you might do:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
If you're not using NSURLConnectionDataDelegate methods, or if you've already addressed this run loop issue, then ignore this counsel, but, bottom line, when you fix the main method in your operation, you might expose the NSURLConnection issue that your old main might have hidden from you.
How does Downloader invoke moveCompleteDownloadedWithUrl?
BTW, you're not showing how Downloader could possibly invoke moveCompleteDownloadedWithUrl. That looks suspicious, but I'm just hoping you simplified your code when you posted it. But if you're not using a protocol-delegate pattern or completion block pattern, then I'd be very nervous about how your multiple Downloader objects are informing the respective MyDownloadOperation objects that the download is done. Personally, I might be inclined to refactor these two differ classes into one, but that's a matter of personal taste.
You can use NSTimer to check whether your download is completed or not. It'll not freezes your UI
NSTimer *localTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(checkDownloadStatus) userInfo:nil repeats:YES];
-(void)checkDownloadStatus
{
if ([_downloadState isEqualToString:#"COMPLETED"])
{
NSLog(#"movie downloaded successfully");
[localTimer invalidate];
}
}
Im trying to subclass a NSOperation, and read some sample from,
they say: when the task finished, using KVO of NSOperation, to finish the operation,
code here:
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"]
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
then isFinished get called
- (BOOL) isFinished{
return(finished);
}
anyone could explain this to me? why isFinished gets called, will the isFinished finish the operation? as I understanded, do KVO manually need [self didChangeValueForKey:#"isExecuting"]; and I didnt see code like addobserver: and observeValueForKeyPath:
I write
-(void)call
{
[self willChangeValueForKey:#"isVip"];
[self didChangeValueForKey:#"isVip"];
}
-(void)isVip
{
NSLog(#"Im vip");
}
isVip is not called when do [self call];
The NSOperationQueue implementation will observe the "isFinished" property of your operation (using KVO) so it knows when to remove it from the queue. isFinished is most likely being called by internal Apple code after it has been told of the change to its value.
Adding to quellish answer, this is how you would override executing, finished, cancelled.
//.m
#interface MyOperation () //class extension, make these otherwise read-only properties read-write, we must synthesize
#property(atomic, assign, readwrite, getter=isExecuting) BOOL executing;
#property(atomic, assign, readwrite, getter=isFinished) BOOL finished;
#property(atomic, assign, readwrite, getter=isCancelled) BOOL cancelled;
#end
#implementation CoreLocationOperation
#synthesize executing, finished, cancelled;
+ (BOOL)automaticallyNotifiesObserversForKey {
return YES;
}
+ (NSSet *)keyPathsForValuesAffectingIsCancelled {
NSSet *result = [NSSet setWithObject:#"cancelled"];
return result;
}
+ (NSSet *)keyPathsForValuesAffectingIsExecuting {
NSSet *result = [NSSet setWithObject:#"executing"];
return result;
}
+ (NSSet *)keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:#"finished"];
return result;
}
- (void)start {
//..
//You can use self.executing = YES; (note we can change executing which would otherwise be read-only because we synthesized our own ivar.
[self setExecuting:YES];
...
}
- (void)cancel {
//..
//super will change the properties executing/finished for us or we can do it manually
[super cancel];
...
}
#end
I think this is clearer than having
[self willChangeValueForKey:_NSURLOperationIsFinished];
[self setIsFinished:YES];
[self didChangeValueForKey:_NSURLOperationIsFinished];
First, you should not need to do manual KVO notifications. For an NSOperation subclass KVO notifications should be sent automatically unless your class has opted out of automatic KVO notifications by implementing +automaticallyNotifiesObserversForKey or +automaticallyNotifiesObserversOf<Key> to return NO.
NSOperation subclasses are not very useful unless they are added to an NSOperationQueue. When an operation is added to a queue, the queue uses KVO to observe the properties that indicate state changes, such as finished, executing, cancelled, etc. Note that these are not isFinished, isExecuting, or isCancelled - those are the names of the synthesized get accessors for those properties.
In your question you include this code:
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"]
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
What you're doing here is sending manual KVO notifications for the get accessors, not the properties being observed. Instead, this would accomplish what you seem to be trying to do:
[self setFinished:YES];
[self setExecuting:NO];
Rather than accessing instance variables directly, use the accessor methods. This will correctly send automatic change notifications for these properties.
If you are truly paranoid about KVO and want to send notifications for the get accessor key paths such as isFinished, register your property as a dependency of the key path:
+ (NSSet *) keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:#"finished"];
return result;
}
Registering dependencies is part of KVO compliance.
I have a UITextfield and a UIButton. The user can enter, for example, search word such as "dog" or "cat" and it will trigger a method in another class that runs on a custom dispatch GCD queue to fetch the images (around 100 or so).
Everything works fine, except if the user in the midst of fetching, decides to change and enter another search word such as "cat" and then press the fetch button, I would like to be able to stop that thread / method while it is fetching the images from the previous search term.
I have thought about NSThread (something I never used before) or blocks (to get notified once the method has finished running), but the problem with blocks is, I will get notified once the method had finished doing its thing, but what I need here is to tell it to stop fetching (because the user has decided on another search and entered another search term).
Can someone please cite me with some samples, as to how we can be able to stop a loop / method while it is running on a custom GCD thread before it is finished? Thanks in advance.
I'm using NSOperationand NSOperationQueue to cluster markers on a map in the background and to cancel the operation if necessary.
The function to cluster the markers is implemented in a subclass of NSOperation:
ClusterMarker.h:
#class ClusterMarker;
#protocol ClusterMarkerDelegate <NSObject>
- (void)clusterMarkerDidFinish:(ClusterMarker *)clusterMarker;
#end
#interface ClusterMarker : NSOperation
-(id)initWithMarkers:(NSSet *)markerSet delegate:(id<ClusterMarkerDelegate>)delegate;
// the "return value"
#property (nonatomic, strong) NSSet *markerSet;
// use the delegate pattern to inform someone that the operation has finished
#property (nonatomic, weak) id<ClusterMarkerDelegate> delegate;
#end
and ClusterMarker.m:
#implementation ClusterMarker
-(id)initWithMarkers:(NSSet *)markerSet delegate:(id<ClusterMarkerDelegate>)delegate
{
if (self = [super init]) {
self.markerSet = markerSet;
self.delegate = delegate;
}
return self;
}
- (void)main {
#autoreleasepool {
if (self.isCancelled) {
return;
}
// perform some Überalgorithmus that fills self.markerSet (the "return value")
// inform the delegate that you have finished
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(clusterMarkerDidFinish:) withObject:self waitUntilDone:NO];
}
}
#end
You could use your controller to manage the queue,
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.name = #"Überalgorithmus.TheKillerApp.makemyday.com";
// make sure to have only one algorithm running
self.operationQueue.maxConcurrentOperationCount = 1;
to enqueue operations, kill previous operations and the like,
ClusterMarker *clusterMarkerOperation = [[ClusterMarker alloc] initWithMarkers:self.xmlMarkerSet delegate:self];
// this sets isCancelled in ClusterMarker to true. you might want to check that variable frequently in the algorithm
[self.operationQueue cancelAllOperations];
[self.operationQueue addOperation:clusterMarkerOperation];
and to respond to the callbacks when the operation has finished:
- (void)clusterMarkerDidFinish:(ClusterMarker *)clusterMarker
{
self.clusterMarkerSet = clusterMarker.markerSet;
GMSProjection *projection = [self.mapView projection];
for (MapMarker *m in self.clusterMarkerSet) {
m.coordinate = [projection coordinateForPoint:m.point];
}
// DebugLog(#"now clear map and refreshData: self.clusterMarkerSet.count=%d", self.clusterMarkerSet.count);
[self.mapView clear];
[self refreshDataInGMSMapView:self.mapView];
}
If I remember correctly I used this tutorial on raywenderlich.com as a starter.
I would recommend using NSOperation as it has cancel method which will cancel the current running operation.