Watching for an NSOperation to complete - objective-c

I am using an NSOperationQueue to get some data for my app:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
GetSUPDataOperation *operation = [[GetDataOperation alloc] init];
operation.context = self;
[queue addOperation:operation];
[operation release];
I want to prevent the user from navigating to certain parts of the app until we have finished getting all the data we need.
Is there some way I can watch for the operation to finish and set a flag then?

You can set a delegate for the operation
#interface YourOperation : NSOperation {
id target;
SEL selector;
}
- (id)initWithTarget:(id)theTarget action:(SEL)action;
#end
At the end of your operation (ie. inside main function), use
- (void)main {
Your code here...
...
[target performSelectorOnMainThread:selector withObject:nil waitUntilDone:NO];
}
to ask your delegate to set a flag

From an architectural point of view you don't want to "monitor" an operation for its running state. You'd want to invoke a method when an operation has finished running.
So just invoke a method that updates the UI (or some other part of the application) when the operation finished.

Related

How to use background queue for Google Drive service in Objective C

According to documentation of google-api-objectivec-client library:
Queries made from any thread can be called back on a background thread by providing a background queue, as in this example:
service.delegateQueue = [[NSOperationQueue alloc] init];
When a delegate queue is specified, there is no requirement for a run loop to be running on the thread that executes the query.
But, it does not work. Handlers are still executed on a main thread.
Question:
How to tell Google Drive service to execute handlers on the background thread?
Code snippet to reproduce
Podfile:
pod 'GTMOAuth2'
pod 'GoogleAPIClient/Drive'
Somewhere in application:
#import "GTLDrive.h"
#import "GTMOAuth2Authentication.h"
...
- (void) applicationDidFinishLaunching:(NSNotification *) aNotification {
service = [[GTLServiceDrive alloc] init];
service.retryEnabled = YES;
service.authorizer = _authorizer //from GTMOAuth2WindowController
service.delegateQueue = [[NSOperationQueue alloc] init];
GTLDriveFile * tempadFolder = [GTLDriveFile object];
folder.name = #"folder-name";
folder.mimeType = #"application/vnd.google-apps.folder";
GTLQueryDrive * query = [GTLQueryDrive queryForFilesCreateWithObject: folder uploadParameters: nil];
[service executeQuery: query completionHandler:
^(GTLServiceTicket * ticket,
GTLDriveFile * updatedFile,
NSError * error) {
if ([NSThread isMainThread]) {
NSLog(#"This is a main thread!");
}
}
}
This bug was fixed in this commit and released in GoogleAPIClient 1.0.2.
For now code behaves according to documentation:
Queries made from any thread can be called back on a background thread by providing a background queue, as in this example
service.delegateQueue = [[NSOperationQueue alloc] init];

Why is nsoperation working serially?

I am using the following code for nsoperation.The problem is all three tasks run serially.What can I do to make the tasks run in parallel.I tried implementing the start and isconcurrent methods but it doesnt work.please help...
Given is my uaview controller class
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Store *S=[ [Store alloc] init];
S.a=25;
NSOperationQueue *someQueue = [NSOperationQueue currentQueue];
someQueue.MaxConcurrentOperationCount = 3;
NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(ymain)
object:nil];
NSInvocationOperation *invocationOp3 = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(ymain2)
object:nil];
NSInvocationOperation *invocationOp4 = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(ymain3)
object:nil];
[someQueue addOperation:invocationOp2];
[someQueue addOperation:invocationOp3];
[someQueue addOperation:invocationOp4];
}
-(void)ymain
{
for (int i = 0 ; i < 10000 ; i++) {
NSLog(#"in the A main"); }
}
This is the other class which was subclassed
#interface A : NSOperation
#end
#implementation A
bool executing;
bool finished;
-(void)main
{
}
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isReady
{
return YES;
}
currentQueue is returning the main queue, which is a serial queue that executes on the main runloop. You should create your own NSOperationQueue to run the operations concurrently.
NSOperationQueue manages the number of operations depending on various factors. This is an implementation detail which you cannot effect. You cannot force it to perform operations concurrently.
The only influence you can have is to set operation dependancy, which affects the order in which operations are performed serially (which isn't much use to you!)
Also currentQueue will return nil when it is called from outside of an NSOperation. If you use mainQueue then you'll get the queue which always runs on the main thread and only runs one operation at one. You need to create a new queue.

performBlockAndWait creates deadlock

I am writing a function that performs some CoreData stuff. I want the function to return only after all the CoreData operations have executed. The CoreData stuff involves creating an object in a background context, then doing some more stuff in the parent context:
+ (void) myFunction
NSManagedObjectContext *backgroundContext = [DatabaseDelegate sharedDelegate].backgroundContext;
[backgroundContext performBlockAndWait:^{
MyObject *bla = create_my_object_in:backgroundContext;
[backgroundContext obtainPermanentIDsForObjects:[[backgroundContext insertedObjects] allObjects] error:nil];
[backgroundContext save:nil];
[[DatabaseDelegate sharedDelegate].parent.managedObjectContext performBlockAndWait:^{
[[DatabaseDelegate sharedDelegate].parent updateChangeCount:UIDocumentChangeDone];
// Do some more stuff
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:someOperation];
}];
}];
return;
}
I want the return to only happen after [queue addOperation:someOperation].
This seems to work most of the cases, but I have had one case when this function never returned. It seemed like it was deadlocked, and I suspect it is because of performBlockAndWait.
My questions are:
(1) Can someone explain why this deadlock occurs?
and
(2) What is the right way of achieving the same functionality? The requirement is that myFunction returns only after both blocks have been executed.
Thank you!
Let's imagine you are calling myFunction from the main thread. Let's imagine [DatabaseDelegate sharedDelegate].parent.managedObjectContext is scheduled on the main thread.
With [backgroundContext performBlockAndWait:] you are scheduling a block on the context private background queue. Blocking the main thread.
With [.parent.managedObjectContext performBlockAndWait:], you are scheduling a block on the main thread, blocking the private queue.
But the main thread is blocked already. So the block will never execute. And performBlockAndWait: will never returns.
Deadlock.
Use asynchronously scheduled blocks, with completion blocks.
You don't have to wait. Your background work executes, then, before it is done, it kicks off work on the main thread, and before it is done, it does your "someOperation." You could replace it with async and it will still work.
Looking at this code, there is no reason to use the blocking versions...
+ (void) myFunction {
NSManagedObjectContext *backgroundContext = [DatabaseDelegate sharedDelegate].backgroundContext;
[backgroundContext performBlock:^{
// Asynchronous... but every command in this block will run before this
// block returns...
MyObject *bla = create_my_object_in:backgroundContext;
[backgroundContext obtainPermanentIDsForObjects:[[backgroundContext insertedObjects] allObjects] error:nil];
[backgroundContext save:nil];
[[DatabaseDelegate sharedDelegate].parent.managedObjectContext performBlock:^{
// Asynchronous, but this whole block will execute...
[[DatabaseDelegate sharedDelegate].parent updateChangeCount:UIDocumentChangeDone];
// Do some more stuff
// This will not run until after the stuff above in this block runs...
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:someOperation];
}];
// You will reach here BEFORE the code in the previous block executes, but
// the "someOperation" is in that block, so it will not run until that
// block is done.
}];
// Likewise, you will reach here before the above work is done, but everything
// will still happen in the right order relative to each other.
return;
}

Synchronous communication using NSOperationQueue

I am new to Objective C programming.
I have created two threads called add and display using the NSInvocationOperation and added it on to the NSOperationQueue.
I make the display thread to run first and then run the add thread. The display thread after printing the "Welcome to display" has to wait for the results to print from the add method.
So i have set the waitUntilFinished method.
Both the Operations are on the same queue. If i use waitUntilFinished for operations on the same queue there may be a situation for deadlock to happen(from apples developer documentation). Is it so?
To wait for particular time interval there is a method called waitUntilDate:
But if i need to like this wait(min(100,dmax)); let dmax = 20; How to do i wait for these conditions?
It would be much helpful if anyone can explain with an example.
EDITED:
threadss.h
------------
#import <Foundation/Foundation.h>
#interface threadss : NSObject {
BOOL m_bRunThread;
int a,b,c;
NSOperationQueue* queue;
NSInvocationOperation* operation;
NSInvocationOperation* operation1;
NSConditionLock* theConditionLock;
}
-(void)Thread;
-(void)add;
-(void)display;
#end
threadss.m
------------
#import "threadss.h"
#implementation threadss
-(id)init
{
if (self = [super init]) {
queue = [[NSOperationQueue alloc]init];
operation = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(display) object:nil];
operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(add) object:nil];
theConditionLock = [[NSConditionLock alloc]init];
}
return self;
}
-(void)Thread
{
m_bRunThread = YES;
//[operation addDependency:operation1];
if (m_bRunThread) {
[queue addOperation:operation];
}
//[operation addDependency:operation1];
[queue addOperation:operation1];
//[self performSelectorOnMainThread:#selector(display) withObject:nil waitUntilDone:YES];
//NSLog(#"I'm going to do the asynchronous communication btwn the threads!!");
//[self add];
//[operation addDependency:self];
sleep(1);
[queue release];
[operation release];
//[operation1 release];
}
-(void)add
{
NSLog(#"Going to add a and b!!");
a=1;
b=2;
c = a + b;
NSLog(#"Finished adding!!");
}
-(void)display
{
NSLog(#"Into the display method");
[operation1 waitUntilFinished];
NSLog(#"The Result is:%d",c);
}
#end
main.m
-------
#import <Foundation/Foundation.h>
#import "threadss.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
threadss* thread = [[threadss alloc]init];
[thread Thread];
[pool drain];
return 0;
}
This is what i have tried with a sample program.
output
2011-06-03 19:40:47.898 threads_NSOperationQueue[3812:1503] Going to add a and b!!
2011-06-03 19:40:47.898 threads_NSOperationQueue[3812:1303] Into the display method
2011-06-03 19:40:47.902 threads_NSOperationQueue[3812:1503] Finished adding!!
2011-06-03 19:40:47.904 threads_NSOperationQueue[3812:1303] The Result is:3
Is the way of invoking the thread is correct.
1.Will there be any deadlock condition?
2.How to do wait(min(100,dmax)) where dmax = 50.
Assuming I'm understanding your question correctly, you have two operations:
Operation A: prints a message, waits for operation B to finish, continues
Operation B: prints a message
If this is the case, can you just print the first message, start operation B, then start operation A?
Also, when you are using NSOperationQueue you don't directly manage threads, it does all the thread management for you. So in your question when you said 'thread' you actually meant to say 'operation'.
To directly answer your question, "Can this cause a deadlock", yes it could. If you change the queue to be sequential instead of concurrent or if you make operation 2 dependent on operation 1 you will probably lock up. I'd recommend not trying to do what you're doing, refactor your code so that one operation doesn't need to pause while the other one works. Based on the code you've posted, there's no reason to structure your code like that.
Hope this might help you, it is the iOS version of WaitForSingleObject in Windows:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[object runSomeLongOperation:^{
// your own code here.
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);

Receiving memory warning when using performSelectorInBackground

I have a UITableView that, when items are selected, loads a viewController, which inside it performs some operations in the background using performSelectorInBackground.
Everything works fine if you slowly tap items in the tableView (essentially allowing the operations preforming in background to finish). But when you select the items quickly, the app quickly returns some memory warnings until it crashes, usually after about 7 or 8 "taps" or selections.
Any idea why this would be? When I move my code from the background thread to the main thread, everything works fine as well. You just can't make the tableView selections as quickly because it's waiting for the operations to finish.
Code snippets:
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
[self performSelectorInBackground:#selector(getOptions) withObject:nil];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
self.options = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
[arrayOnDisk release];
[apool release];
}
Every time you perform the getOptions selector in the background, what's really happening is a new thread is being created on your behalf, and the work is being done there. When the user taps your table cells a bunch of times in a row, a new thread is created each time to handle the work. If the work done by getOptions takes some time to complete, you will have multiple threads calling getOptions at the same time. That is to say, the system doesn't cancel previous requests to perform getOptions in the background.
If you assume that it takes N bytes of memory to perform the work done by getOptions, then if the user taps on five table cells in a row and getOptions doesn't finish right away, then you'll find that your app is using 5 * N bytes at that point. In contrast, when you change your app to call getOptions on the main thread, it has to wait for each call to getOptions to complete before it can call getOptions again. Thus when you do your work on the main thread you don't run into the situation where you're using 5 * N bytes of memory to do the work of five instances of getOptions simultaneously.
That's why you run out of memory when you do this work in the background and the user taps multiple table cells: you're doing multiple instances of the work, and each instance requires its own amount of memory, and when they all get added up, it's more than the system can spare.
It looks like you're just calling getOptions once when the user selects a table cell and navigates into a new view controller. Since the user will only be looking at one of these view controllers at a time, you don't really need to have multiple instances of getOptions going on simultaneously in the background. Instead, you want to cancel the previously-running instance before starting the new one. You can do this using an NSOperationQueue, like so:
- (NSOperationQueue *)operationQueue
{
static NSOperationQueue * queue = nil;
if (!queue) {
// Set up a singleton operation queue that only runs one operation at a time.
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
}
return queue;
}
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
// Cancel any pending operations. They'll be discarded from the queue if they haven't begun yet.
// The currently-running operation will have to finish before the next one can start.
NSOperationQueue * queue = [self operationQueue];
[queue cancelAllOperations];
// Note that you'll need to add a property called operationQueue of type NSOperationQueue * to your LeaseDetailController class.
leaseController.operationQueue = queue;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
// Now we use the operation queue given to us in -showLeaseView:, above, to run our operation in the background.
// Using the block version of the API for simplicity.
[queue addOperationWithBlock:^{
[self getOptions];
}];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
NSMutableArray * resultsArray = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
// Now that the work is done, pass the results back to ourselves, but do so on the main queue, which is equivalent to the main thread.
// This ensures that any UI work we may do in the setter for the options property is done on the right thread.
dispatch_async(dispatch_queue_get_main(), ^{
self.options = resultsArray;
});
[arrayOnDisk release];
[apool release];
}