ios: waiting for method to finish executing before continuing - ios

I am new to IOS development and am currently facing a problem.
When method A is called, it calls method B and then it wait for delegate connectionDidFinish which connectionDidFinish will execute MethodC.
My question is how do I ensure that methodA to methodC has finished executing before executing NSLog?
I found that a way to solve this problem is to use notification center. Send notification to me after finishing executing methodC. I don't think this is a good solution. Is there another way to do this?
Example:
[a methodA];
NSLog(#"FINISH");

If any of those methods perform actions asynchronously, you can't. You'll have to look into a different way of doing this. I personally try to use completion blocks when ever I can, although it's perfectly fine to do this other ways, like with delegate methods. Here's a basic example using a completion block.
- (void)someMethod
{
[self methodAWithCompletion:^(BOOL success) {
// check if thing worked.
}];
}
- (void)methodAWithCompletion:(void (^) (BOOL success))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
completion(ifThingWorked)
});
});
}

In the code you posted, methodA must finish executing before the log statement will execute.
However, if methodA starts an asynchronous process that takes a while to finish and returns before it is finished, then you need to do something different. Usually you don't want to freeze the user interface while you are waiting, so you set up a delegate, pass in a completion block, or wait for an "ok, I'm done" notification.
All those are very valid, good ways to solve the problem of waiting for asynchronous tasks to finish running.
Newer APIs are starting to use completion blocks. Examples are:
presentViewController:animated:completion:, which takes a completion
block that gets called once the new view controller is fully
on-screen and "ready for business.
animateWithDuration:animations:completion:, which takes a completion
block that gets executed once the animation is finished, and
sendAsynchronousRequest:queue:completionHandler:, which starts an
asynchronous URL request (usually an HTTP GET or PUT request) and
provides a completion block that gets called once the request has
been completed (or fails)

Related

Scheduling function call

I have function ,
-(void)serverFetch{
//server fetch
}
In every 15mintutes, i'm calling this method using NSTimer,
[NSTimer scheduledTimerWithTimeInterval:900.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
[self fetchFromServer];
}];
I'm using APNS in my app, so when we receive the notification , again i'm calling this method.
So Scheduler thread and this notification thread should not happen in parallel. For instance, when scheduler thread is in operation and push notification arrives then push notification thread should wait for scheduler thread.How can i achieve this?Any help appreciated?
One approach would be to use Grand Central Dispatch (GCD). Create a serial queue and add blocks to it for asynchronous execution when your timer fires or notifications arrive, the blocks will be executed strictly one after the other. This will only work correct if the work each block does is completely synchronous, that is when the block returns all its work is complete.
If your blocks contain asynchronous work then you will also need a semaphore. A block should acquire the semaphore when it starts execution and its final asynchronous action should release it. In this way though the block scheduled by the serial queue returns and the queue starts the next block that next block will immediately block waiting to acquire the semaphore until the previous block's last asynchronous action releases it.
If after studying GCD, designing a solution, and implementing it you have a problem ask a new question, show the code you have written, and explain the problem. Someone will undoubtedly help you move forward.
HTH

How to genuinely cancel operations

I'm using NSOperation to perform two operations. The first operation is loading the data from Internet, while the second operation is updating the UI.
However, if the viewDidDisappear function is triggered by user, how can I stop the data loading process?
I tried
[taskQueue cancellAllOperations],
but this function only marks every operation inside as cancelled while not literally cancel the executing process.
Could anyone please give some suggestions? Thanks in Advance.
AFAIK, there's no direct way to cancel an already executing NSOperation. But you can cancel the taskQueue like you're doing.
[taskQueue cancellAllOperations];
And inside the operation block, periodically (in between logically atomic block of code) check for isCancelled to decide whether to proceed further.
NSBlockOperation *loadOp = [[NSBlockOperation alloc]init];
__weak NSBlockOperation *weakRefToLoadOp = loadOp;
[loadOp addExecutionBlock:^{
if (!weakRefToLoadOp.cancelled) {
// some atomic block of code 1
}
if (!weakRefToLoadOp.cancelled) {
// some atomic block of code 2
}
if (!weakRefToLoadOp.cancelled) {
// some atomic block of code 3
}
}];
The NSOperation's block should be carefully divided into sub-block, such that it is safe to discontinue the execution of rest of the block. If required, you should also rollback the effects of sub-blocks executed so far.
if (!weakRefToLoadOp.cancelled) {
// nth sub-block
}
else {
//handle the effects of so-far-executed (n-1) sub-blocks
}
Thanks sincerely for your answer. But I find out that actually
[self performSelectorInBackground:#selector(httpRetrieve) withObject:nil];
solve my problem. The process don't have to be cancelled. And feels like NSOpertaions is not running in the background. Thus, back to super navigation view while the nsoperation is still running, the UI will become stuck!

I'm not sure if I'm using NSOperationgQueue's addOperationWithBlock incorrectly

I've been using NSOperationQueue's addOperationWithBlock: to run code in background threads, like so:
self.fetchDataQueue = NSOperationQueue()
for panel in self.panels {
self.fetchDataQueue.addOperationWithBlock() {
() -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
//Background code
}
}
}
I'm concerned that I may be doing this wrong. I can't see a way that the fetch queue would be able to know when an operation is done, since there's no completion to call, and I'm not confident it's tracking activity across threads to make sure it's still going.
And the point of using this is so that I don't queue them up in single file and take much longer to process, and so I don't run them all at once and use too much memory.
EDIT: I'm aware that I don't need to be doing dispatch_async, but it's simply an example of some block-based code I may call which may do the same thing, or a web request which may get back after a delay.
Well, your code will run in a background block. If you are using a queue to make sure that one operation only starts when the next one is finished, you may be in trouble: The block that you happen to the NSOperationQueue has finished as soon as it has dispatched the background code to GCD, not when the background code has actually finished which may be much later.

Waiting XML parse process before calling another method

I am parsing an XML web service and after that parse finishes I want to call another method. But my code calls the method during the parse process. What I want is to wait till the parse process end. Here is my code:
ArsivNoCheck *arsivNoCheck = [ArsivNoCheck alloc];
[arsivNoCheck checkArsivNo:_txtArsivNo.text]; //Here I call parsing method in another class
//Here I call the method
[self performSelectorOnMainThread:#selector(sampleMethod) withObject:nil waitUntilDone:YES];
-(void) sampleMethod
{
//some code
}
You should consider NSOperation, and its method completionBlock.
Then, you would be able to perform your parsing, and at the end of it, execute some code.
Note : If you plan to update the UI, take care, because the completionBlock is not necessarily running on the main thread!
From NSOperation's Doc reference :
completionBlock
Returns the block to execute when the operation’s main
task is complete.
-(void (^)(void))completionBlock
Return Value
The block to execute after the operation’s main task is completed. This block takes no
parameters and has no return value.
Discussion
The completion block you provide is executed when the value
returned by the isFinished method changes to YES. Thus, this block is
executed by the operation object after the operation’s primary task is
finished or cancelled.
Example :
[filterOp setCompletionBlock: ^{
NSLog(#"Finished filtering an image.");
}];
See this tutorial on Ray Wenderlich's site for implementation.

NSOperation deadlocks and blocks NSOperationQueue

I use a subclass of NSOperation to upload large files to AWS S3 using Amazon's iOS SDK (v1.3.2). This all works fine, but some beta testers experience deadlocks (iOS 5.1.1). The result is that the NSOperationQueue in which the operations are scheduled is blocked as only one operation is allowed to run at one time. The problem is that I cannot reproduce the issue whereas the beta testers experience this problem every single time.
The operation is quite complex due to how the AWS iOS SDK works. However, the problem is not related to the AWS iOS SDK as far as I know based on my testing. The operation's main method is pasted below. The idea of the operation's main method is based on this Stack Overflow question.
- (void)main {
// Operation Should Terminate
_operationShouldTerminate = NO;
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self isPreparingUploadWithUuid:self.uuid];
});
// Increment Network Activity Count
[self incrementNetworkActivityCount];
// Verify S3 Credentials
[self verifyS3Credentials];
while (!_operationShouldTerminate) {
if ([self isCancelled]) {
_operationShouldTerminate = YES;
} else {
// Create Run Loop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
// Decrement Network Activity Count
[self decrementNetworkActivityCount];
NSLog(#"Operation Will Terminate");
}
The method that finalizes the multipart upload sets the boolean _operationShouldTerminate to YES to terminate the operation. That method looks like this.
- (void)finalizeMultipartUpload {
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self didFinishUploadingUploadWithUuid:self.uuid];
});
// Operation Should Terminate
_operationShouldTerminate = YES;
NSLog(#"Finalize Multipart Upload");
}
The final log statement is printed to the console, but the while loop in the main method does not seem to exit as the final log statement in the operation's main method is not printed to the console. As a result, the operation queue in which the operation is scheduled, is blocked and any scheduled operations are not executed as a result.
The operation's isFinished method simply returns _operationShouldTerminate as seen below.
- (BOOL)isFinished {
return _operationShouldTerminate;
}
It is odd that the while loop is not exited and it is even more odd that it does not happen on any of my own test devices (iPhone 3GS, iPad 1, and iPad 3). Any help or pointers are much appreciated.
The solution to the problem is both complex and simple as it turns out. What I wrongly assumed was that the methods and delegate callbacks of the operation were executed on the same thread, that is, the thread on which the operation's main method was called. This is not always the case.
Even though this was true in my test and on my devices (iPhone 3GS), which is why I did not experience the problem myself. My beta testers, however, used devices with multicore processors (iPhone 4/4S), which caused some of the code to be executed on a thread different from the thread on which the operation's main method was invoked.
The result of this is that _operationShouldTerminate was modified in the finalizeMultipartUpload method on the wrong thread. This in turn means that the while loop of the main method was not exited properly resulting in the operation deadlocking.
In short, the solution is to update _operationShouldTerminate on the same thread as the main method was invoked on. This will properly exit the while loop and exit the operation.
There are a number of problems with your code, and I can offer two solutions:
1) read up on Concurrent NSOperations in Apple's Concurrency Programming Guide. To keep the runLoop "alive" you have to add either a port or schedule a timer. The main loop should contain a autorelease pool as you may not get one (see Memory Management in that same memo). You need to implement KVO to let the operationQueue know when your operation is finished.
2) Or, you can adopt a small amount of field tested hardened code and reuse it. That Xcode project contains three classes of interest to you: a ConcurrentOperation file that does well what you are trying to accomplish above. The Webfetcher.m class shows how to subclass the concurrent operation to perform an asynchronous URL fetch from the web. And the OperationsRunner is a small helper file you can add to any kind of class to manage the operations queue (run, cancel, query, etc). All of the above are less than 100 lines of code, and provide a base for you to get your code working. The OperationsRunner.h file provide a "how to do" too.

Resources