My code will receive a push notification containing the URL of a remote image to download and display and I was planning on placing a call to NSData:dataWithContentOfURL in an operation queue to run separately from the main thread.
But I'm not clear on the lifetime of the operation and when/how I should delete it.
For example suppose I have code similar to this:
- (void) onReceiptOfPushNotification:(NSURL*) url
{
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperationWithBlock: ^{
NSData* data = [NSData dataWithContentsOfURL: url];
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
do stuff with the data and image
}
}
}
My question is as q is a local variable of onReceiptOfPushNotification then when and how to delete it once the operation has completed?
As per Apple Documentation, Operations are removed from the queue only when they finish executing. However, in order to finish executing, an operation must first be started. Because a suspended queue does not start any new operations, it does not remove any operations (including cancelled operations) that are currently queued and not executing.
So after an NSOperation performed the task to completion, it will be automatically removed from the queue.
Please refer the link here, that has each & every detail.
Hope that helps.
Push notification has a delegate that gets called application:didReceiveRemoteNotification: on every notification. You could make your operation object a singleton and reset it in the delegate.
Related
Help me out here or just shed some light on the problem.
I have a scenario where I perform a sync of archived messages on a openfire server and I handle and store all incoming messages with NSOperations and NSOperationQueue.
I want to get notified when the NSOperationQueue is done, but I can't simply count the number of operations it has running. At times the NSOperationQueue has 0 operations because it depends on data to arrive form the server.
The NSOperations start methods
- (void)startArchiveSyncStore:(XMPPIQ *)iq operationID:(NSString *)xmlID {
#autoreleasepool {
if (![self.pendingOperations.archiveStoreInProgress.allKeys containsObject:xmlID]) {
ArchiveStoreOperation *storeOperation = [[ArchiveStoreOperation alloc] initWithMessagesToArchive:iq withID:xmlID delegate:self];
[self.pendingOperations.archiveStoreInProgress setObject:storeOperation forKey:xmlID];
[self.pendingOperations.archiveStoreQueue addOperation:storeOperation];
}
}
}
- (void)startArchiveSycnDownload:(XMPPIQ *)iq operationID:(NSString *)xmlID {
#autoreleasepool {
if (![self.pendingOperations.archiveDownloadInProgress.allKeys containsObject:xmlID]) {
ArchiveDownloadOperation *downloadOperation = [[ArchiveDownloadOperation alloc] initWithMessagesToDownload:iq withID:xmlID delegate:self];
[self.pendingOperations.archiveDownloadInProgress setObject:downloadOperation forKey:xmlID];
[self.pendingOperations.archiveDownloadQueue addOperation:downloadOperation];
}
}
}
And this is the main thread callback performed by the NSOperation:
- (void)archiveStoreDidFinish:(ArchiveStoreOperation *)downloader {
NSString *xmlID = downloader.xmlnsID;
DDLogInfo(#"%# %#", THIS_METHOD, xmlID);
[self.pendingOperations.archiveStoreInProgress removeObjectForKey:xmlID];
}
These operations start when I receive iq stanzas containing lists of the chat history from the openfire server. Then I handle these lists like so:
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq {
if ([iq isResultIQ]) {
if ([iq elementForName:#"list" xmlns:#"urn:xmpp:archive"]) {
[self startArchiveSycnDownload:iq operationID:[[iq attributeForName:#"id"] stringValue]];
}
if ([iq elementForName:#"chat" xmlns:#"urn:xmpp:archive"]) {
[self startArchiveSyncStore:iq operationID:[[iq attributeForName:#"id"] stringValue]];
}
}
return NO;
}
Any ideas folks ? Thanks in advance...
From my understanding each NSOperation has an isFinished property that you can check for. But, there is a caveat - isFinished doesn't guarantee that the operation has completed successfully. It is set to true if it succeeds but also if it has been cancelled or an error has occurred.
Obviously each queue has a count of the operations [queue.operations count] but as you've said that won't be of use here.
One alternative is to use KVO. You could try setting this up between the other object that you're using and the NSOperationQueue. You could add an observer to the queue and check that no other operations are in effect.
Also, check this SO post here if you haven't already.
I use NSNotificationCenter and post whenever a NSOperation in the last queue finishes. I assume that there is a "last" queue, aka the one that gets spun up after other queue operations have finished.
When you receive the notification check the count of all your NSOperationQueues to see if they are empty.
It's not clear from your question exactly what condition you do consider to be "done" (no operations in the queue and… what?).
One approach is to create a completion operation. As you create the other operations, add each as a dependency of the completion operation. The completion operation can be in some other queue, possibly [NSOperationQueue mainQueue]. When there are no other operations outstanding, the completion operation will execute.
If you have some other condition than other operations outstanding that means the queue is not "done", then you need to explain. If it's that network downloading is in progress, then maybe you need to wrap such downloading in an operation.
You could also use a custom subclass of NSOperation for the completion operation and override -isReady to use whatever criteria it wants to augment the superclass's notion of readiness. Of course, if you do that, you need to generate KVO change notifications when those other criteria change.
In my code, I am running a local server (CocoaHTTPServer). When the server receives a request, it creates a thread and passes control to a certain method ( - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path, perhaps irrelevant here).
I need to read a list of local assets and return the result. The API call ( [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) ... ) is asynchronous.
Since the HTTPResponse needs to wait until the API call has finished, I have created a flag called _isProcessing , which I set before making the API call. After the call is finished, I am unsetting the flag and returning the HTTP request. The code to wait looks like:
// the API call is non-blocking. hence, wait in a loop until the command has finished
samCommand->isProcessing = YES;
while (samCommand->isProcessing) {
usleep(100*1000);
}
The API call calls a delegate method upon finishing its task as follows:
// to be called at the end of an asynch operation (eg: reading local asset list)
- (void) commandDidFinish {
// flag to open the lock
isProcessing = NO;
}
This works, but will perhaps require performance enhancements. How can I use anything (run-loop etc) here to improve upon the performance.
Edit following weichsel solution using dispatch_semaphore
Following weichsel's solution, I created a semaphore. The sequence of my code is:
CocoaHTTPServer receives a request and hence creates a new thread
It calls the static method of a Command class to execute the request
The Command class creates a new command Object calls another class (using reflection) which calls ALAsset APIs and passes the command Object to it
Upon returning, the ALAsset API call calls the delegate method of
the command class
I have hence embedded semaphores in appropriate locations. However, the semaphore's wait loop just doesnt end sometimes. The normal output should be:
2014-02-07 11:27:23:214 MM2Beta[7306:1103] HTTPServer: Started HTTP server on port 1978
2014-02-07 11:27:23:887 MM2Beta[7306:6303] created semaphore 0x1f890670->0x1f8950a0
2014-02-07 11:27:23:887 MM2Beta[7306:6303] calling execute with 0x1f890670
2014-02-07 11:27:23:887 MM2Beta[7306:6303] starting wait loop 0x1f890670->0x1f8950a0
2014-02-07 11:27:23:887 MM2Beta[7306:907] calling getAssetsList with delegate 0x1f890670
2014-02-07 11:27:24:108 MM2Beta[7306:907] calling delegate [0x1f890670 commandDidFinish]
2014-02-07 11:27:24:108 MM2Beta[7306:907] releasing semaphore 0x1f890670->0x1f8950a0
2014-02-07 11:27:24:109 MM2Beta[7306:6303] ending wait loop 0x1f890670->0x0
In every few runs, the last step ( ending wait loop 0x1f890670->0x0 doesnt occur). Hence, the wait loop never ends. Sometimes the code crashes too, exactly at the same point. Any clue what is wrong here.
My code is as follows:
#implementation SAMCommand {
NSData* resultData;
dispatch_semaphore_t semaphore; // a lock to establish whether the command has been processed
}
// construct the object, ensuring that the "command" field is present in the jsonString
+(NSData*) createAndExecuteCommandWithJSONParamsAs:(NSString *)jsonString {
SAMCommand* samCommand = [[SAMCommand alloc] init];
samCommand.commandParams = [jsonString dictionaryFromJSON];
if(COMPONENT==nil || COMMAND==nil){
DDLogError(#"command not found in %#",jsonString);
return nil;
}
samCommand->semaphore = dispatch_semaphore_create(0);
DDLogInfo(#"created semaphore %p->%p",samCommand,samCommand->semaphore);
// to execute a command contained in the jsonString, we use reflection.
DDLogInfo(#"calling execute with %p",samCommand);
[NSClassFromString(COMPONENT) performSelectorOnMainThread:NSSelectorFromString([NSString stringWithFormat:#"%#_%#_%#:",COMMAND,MEDIA_SOURCE,MEDIA_TYPE]) withObject:samCommand waitUntilDone:NO];
// the above calls are non-blocking. hence, wait in a loop until the command has finished
DDLogInfo(#"starting wait loop %p->%p",samCommand,samCommand->semaphore);
dispatch_semaphore_wait(samCommand->semaphore, DISPATCH_TIME_FOREVER);
DDLogInfo(#"ending wait loop %p->%p",samCommand,samCommand->semaphore);
DDLogInfo(#"");
// return the data
return samCommand->resultData;
}
// to be called at the end of an asynch operation (eg: reading local asset list)
- (void) commandDidFinish {
// flag to release the lock
DDLogInfo(#"releasing semaphore %p->%p",self,semaphore);
dispatch_semaphore_signal(semaphore);
semaphore = nil;
}
#end
I got it to work :)
Finally, what seems to work stably is creating the semaphore, and passing it to the ALAsset asynch API calls, and releasing it at the end of the call. Earlier, I was calling a delegate method of the class where I had created the semaphore, and the semaphore object was somehow getting releases. Unsure of what was happening there really.
You can use semaphore to block execution of the current queue until another one returns.
The basic pattern is:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[assetsLibrary enumerateAssetsUsingBlock^(ALAsset *result, NSUInteger index, BOOL *stop):^{
...
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
You can find a full example of this method in Apple's MTAudioProcessingTap Xcode Project:
https://developer.apple.com/library/ios/samplecode/AudioTapProcessor
The relevant lines start at MYViewController.m:86
NSRunLoop has a method called runUntilDate: which should work for you with dates in near future like 1s ahead or so. That way you can replace your sleep call in the while loop for example with:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeintervalSinveNow:1]
I created an NSOperation which goal is to download a few images (like 20) from 20 URLs.
So inside this NSOperation I create 20 AFImageRequestOperation add them in an NSOperationQueue and call -waitUntilAllOperationsAreFinished on the queue.
Problem is, it doesn't wait, it returns instantly. Here is the code
- (void)main {
NSArray *array = [I have the 20 links stored in this array];
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < array.count; i++) {
NSURL *url = [NSURL URLWithString:[array objectAtIndex:i]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFImageRequestOperation *op = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:^UIImage *(UIImage *image) {
return image;
} success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// SUCCESS BLOCK (not relevant)
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// FAILURE BLOCK (not relevant)
}];
[self.queue addOperation:op];
}
[self.queue waitUntilAllOperationsAreFinished]; // Here is the problem it doesn't wait
DDLogWarn(#"-- %# FINISHED --", self);
}
In the console, the DDLogWarn(#"-- %# FINISHED --", self); prints before every operations even started, so my guess was that the waitUntilAllOperationsAreFinished didn't do its job and didn't wait, but the 20 Operations are still running after that which may means that the main didn't return yet, so I don't know what to think anymore.
EDIT 1 : Actually I'm wondering, if my DDLog inside the success and failure blocks because waitUntilAllOperationsAreFinished is blocking the thread until all operations complete. That may explain why I don't see anything in happening and everything suddenly.
I have found it useful to create an NSOperation that contains other NSOperations for similar reasons to you, i.e. I have a lot of smaller tasks that make up a bigger task and I would like to treat the bigger task as a single unit and be informed when it has completed. I also need to serialise the running of the bigger tasks, so only one runs at a time, but when each big task runs it can perform multiple concurrent operations within itself.
It seemed to me, like you, that creating an NSOperation to manage the big task was a good way to go, plus I hadn't read anything in the documentation that says not to do this.
It looks like your code may be working after all so you could continue to use an NSOperation.
Depending on your circumstances blocking the thread may be reasonable. If blocking isn't reasonable but you wanted to continue using an NSOperation you would need to create a "Concurrent" NSOperation see Concurrency Programming Guide: Configuring Operations for Concurrent Execution
If you only allow one image download at a time, you could use #jackslashs suggestion to signal the end of the operation, or if you want to allow concurrent image downloads then you could use a single NSBlockOperation as the final operation and use -[NSOperation addDependency:] to make it dependant on all the other operations so it would run last.
When you get the signal that everything is finished and you can set the isFinished and isExecuting flags appropriately as described in the documentation to finalise your main NSOperation.
Admittedly this has some level of complexity, but you may find it useful because once that complexity is hidden inside an NSOperation the code outside may be simpler as was the case for me.
If you do decide to create a Concurrent NSOperation you may find the Apple sample code LinkedImageFetcher : QRunLoopOperation useful as a starting point.
This code doesn't need to be inside an NSOperation. Instead make a class, perhaps a singleton, that has an operation queue and make a method called
-(void)getImagesFromArray:(NSArray *)array
or something like that and your code above will work fine enqueueing onto that queue. You don't need to call waitUntilAllOperationsAreFinished. Thats a blocking call. If your queue has a max operation count of 1 you can just add another operation to it once you have added all the network operations and then when it executes you know all the others have finished. You could just add a simple block operation on to the end:
//add all the network operations in a loop
[self.operationQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
//this is the last operation in the queue. Therefore all other network operations have finished
}]];
A newbie question, please bear with me. I cannot makes sense of what is a "calling queue". I search around but cannot find a definition. Maybe it's too simple to deserve a definition? For example, Apple document says the following completion handler is "invoked on the calling queue". Could anyone tell me which is the calling queue in the following code?
Edit: I am familiar with GCD and queue and thread concepts. Just not sure about the calling queue..
dispatch_async(DownloadQ, ^{
//
// Download (json to map to array)
//
NSArray* array = [DoubanDownloader downloadEvent];
//
// Map (Do NOT do this in main queue, will block UI)
//
[FillDatabase mapArray:array toManagedObjectsinContext:self.document.managedObjectContext byCommand:YES];
//
// commit changes to store
//
[self.document.managedObjectContext performBlock:^{ // This will get main thread!
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success){
// what's the calling queue?
}];
}];
});
The calling queue is whichever queue makes the call to saveToURL:forSaveOperation:completionHandler:. So e.g. if you call that from the main queue then your completion handler will also occur on the main queue.
So 'calling' is an adjective, rather than 'calling queue' being a compound noun.
In my app, I use operations to perform time-intensive tasks, so my user interface won't freeze. For that, I use NSInvocationOperation. I wanted to test the overall architecture first before implementing the code to actually complete the tasks, so that's what I have right now:
// give the object data to process
- (void)processData:(NSObject*)dataToDoTask {
... // I store the data in this object
NSInvocationOperation *newOperation =
[[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(performTask)
object:nil];
[[NSOperationQueue mainQueue] addOperation:newOperation];
...
}
// process data stored in the object and return result
- (NSObject*)performTask {
[NSThread sleepForTimeInterval:1]; // to emulate the delay
return [NSString stringWithFormat:#"unimplemented hash for file %#", self.path];
}
However, the sleep doesn't work as I expect: instead of delaying the operation completetion, it freezes the app. It seems that I either operations or sleep incorrectly, but I can't figure out which and how.
That is because you are running your operation on the main thread (the same running your user interface).
If you want to run your operation concurrently, create a new operation queue:
NSOperationQueue * queue = [NSOperationQueue new];
[queue addOperation:newOperation];