How do i cancel a background block operation in iOS? - ios

I want to know what is the proper way to use [self.operationQueue cancelAllOperations];
I am using a block operation in async to fetch results from my API. Sometimes it happens that I get result of first request after second request and those are displayed to user.
I am using AFNetworking library for operations. Any suggestion on how I can make sure that only one request (the latest one) is active at a particular time, and previous one gets cancelled automatically.

When all operations in a queue are cancelled it is the responsibility of each running operation to stop itself. The queue will only prevent future operations from starting. With block operations there isn't really any way to stop as the block doesn't have access to the operation to check if it's cancelled.
It isn't clear exactly what you're using the operation for, but you would need to crate an operation subclass, either to run or to wrap that logic, which at least checked for cancellation before running the final callback to return the result.

Related

Synchronous API requests with Queue in Swift?

I need to execute synchronous requests on API using Swift. Requests must be queued. Meaning, if one is already in progress and it awaits response it must not be canceled or interrupted by the next synchronous request that enters queue or is already in queue.
Requests must be executed in order as they enter queue (FIFO). Next request must not start until previous is finished/completed in the queue.
Also, every single request in queue must be executed until queue is empty. Synchronous requests can enter queue at any time.
I meant to implement a Synchronous API Client as a singleton which contains its own Queue for queued requests. Requests must not stop/freeze UI. UI has to be responsive on user interaction all the time.
I know it can be done with semaphores but, unless you know what your are doing and you are completely sure how semaphores work, it is not the safest or maybe the best way do it. Otherwise, potential bugs and crashes could appear.
I'm expecting successful execution of every synchronous request that enters queue (by FIFO order, regardless if it returns success or an error as a response) and UI updates immediately after.
So, my question is what is the best way to approach and solve this problem?
Thanks for your help and time.
You can create your own DispatchQueue and put you operations on it as DispatchWorkItems. It is serial per default. Just remember to call your completions on DispatchQueue.main if you plan to update the UI.
John Sundell has a wonderful article about DispatchQueues here:
https://www.swiftbysundell.com/articles/a-deep-dive-into-grand-central-dispatch-in-swift/

Best way to ensure an initial network request is completed before other requests are sent (iOS app)

An app I am working on requires creating a container object on a server and inserting items into that container. I don't want to create the container object until the first item needs to be inserted. However, creating the container object requires some initialization that may take a little time. While that container is still initializing the user can still to send insertion requests that aren't getting handled because the container isn't ready yet. I have two main questions:
Should this be dealt with on the client or server side?
What is the best practice for dealing with kind of this issue?
Essentially, I need to ensure my initial createContainer data task in complete before any insertItem requests are sent.
Addition Information
An insertItem request is sent by clicking on a corresponding tableViewCell. The first tableViewCell a user clicks on sends a createContainer request that creates a container holding the first item.
For a container holding n items, the request should be sent in the following order:
createContainer(Container(with: item1)
insertItem(item2)
...
insertItem(itemn)
After the first request completes, the remaining n – 1 requests may complete in any order.
My Thoughts
It sounds like I want the createContainer request to be handled synchronously while the insertItem request should be handled asynchronously. I'm not sure if that is the best approach or even how to perform that appropriately, so any guidance would be greatly appreciated.
You can use a NSOperationQueue and multiple NSOperations to implement your desired behavior. A NSOperation instance can be dependent on the completion of another NSOperation instance:
dependencies
An array of the operation objects that must finish
executing before the current object can begin executing.
For your example this would mean that the insertItem-Operations are dependent on the createContainer operation.
When you add all those operations to a NSOperationQueue your createContainer operation will run first. When it has finished, the other operations will start running as their dependencies are now satisfied. You can also control how many operations you want to run concurrently using maxConcurrentOperationCount on NSOperationQueue.
As you will be using asynchronous API in your NSOperations you will need to implement a ConcurrentOperation and handle the state changes yourself. The API Reference is explaining this in pretty good detail.
Check out the API Reference for NSOperation for further information.
There is also a nice NSHipster article on NSOperations.
Adding to the NSOperationQueue answer, it's sometimes difficult to manually manage all the state changes that an NSOperation requires to handle something asynchronous like a network call.
To simplify that, you can use a Swift Library called Overdrive. It's an amazing library in which you simply subclass a Task class and write your network code in the run() function. And when you're done, you simply call self.finish to finish the task. Here's an example: Just create a simple download task:
Then, just add it to the queue.
You can also add dependencies between tasks, which basically solves your use case.
Hope this helps.

does calling cancelPreviousPerformRequestsWithTarget: on a selector cancel internal method calls as well?

I have a selector method which performs search for particular text in two different libraries in two different threads called using dispatch_async.
Now this selector is bound to a textfield and as soon some characters change we can query the libraries for the text.
Now a search takes some time say like 0.3 - 0.4 second and if the first search is not complete before another character is entered I woould like to cancel the search and re-start with new characters in the text field.
So does calling cancelPreviousPerformRequestsWithTarget on the selector cancel the internal threads and libraries call...?
No. cancelPreviousPerformRequestsWithTarget has nothing to do with blocks dispatched via GCD (i.e. dispatch_async). It cancels previous invocations of
selectors scheduled for a later time on a specific NSRunLoop using -performSelector:withObject:afterDelay:. Furthermore, it can't cancel those invocations if they're already in progress, it can only prevent them from starting, if they're still waiting to begin.
There is no means by which to (safely) forcibly cancel in flight operations, regardless of the method used to dispatch them. The operation has to, itself, support cancel-ability, usually by checking a flag periodically during its work, and returning early if the flag says the operation should cancel.
Because someone will inevitably come along and say that NSOperation supports cancelation, I might as well get it out of the way now, by pointing out that NSOperation's cancelation support still requires the operation being canceled to periodically check a flag and intentionally return early, it's just that NSOperation has the cancelled property which provides the flag for you. For that to be useful to you, your code has to know that it's executing as part of an NSOperation, and it has to have a pointer to the specific NSOperation it's executing as part of, and it still has to periodically check the cancelled property of that NSOperation and return early in order to "support cancellation."
There is no free lunch for cancellation on non-garbage-collected runtimes.

How to remove queued block from a GCD dispatch queue?

I am trying to re-schedule queued block that will handle the update operations.
Main goal is updating UI objects (online user table...) with minimum amount of (UI update request). (Server sometimes rain down massive amount of updates, yay!)
For simplicity main scenario is;
The dispatch_queue_t instance (queue that will handle given UI updating block) is a serial dispatch queue (private dispatch queue)
The operation (UI updating block) is scheduled with dispatch_after with t amount of time (Instead of updating for each data set update, collect update requests within t amount of time and perform a single UI update for them)
In case our data set updated, check if there already exist a scheduled event. If yes, unschedule it from dispatch_queue_t instance. Then re-schedule same block with t amount of time delay.
Also;
t is a small amount of time interval that possibly won't be noticed by the user (like 500 ms.)
Any alternative approach is welcome.
My motive behind this;
i applied same logic via Android's Handler (post & removeCallbacks combination with Runnable instance) and i hope i could achieve the same on iOS.
Edit:
As #Sven suggested usage of NSOperationQueue is more suitable for the scenario as they support cancelling each NSOperation. I skimmed through documents and found;
Canceling Operations
Once added to an operation queue, an operation object is effectively owned by the queue and cannot be removed. The only way to dequeue an operation is to cancel it. You can cancel a single individual operation object by calling its cancel method or you can cancel all of the operation objects in a queue by calling the cancelAllOperations method of the queue object.
You should cancel operations only when you are sure you no longer need them. Issuing a cancel command puts the operation object into the “canceled” state, which prevents it from ever being run. Because a canceled operation is still considered to be “finished”, objects that are dependent on it receive the appropriate KVO notifications to clear that dependency. Thus, it is more common to cancel all queued operations in response to some significant event, like the application quitting or the user specifically requesting the cancellation, rather than cancel operations selectively.
This can easily be done with GCD as well, no need to reach for the big hammer that is NSOperationQueue here.
Just use a non-repeating dispatch timer source directly instead of dispatch_after (which is just a convenience wrapper around such a timer source, it doesn't actually enqueue the block onto the queue until the timer goes off).
You can reschedule a pending timer source execution with dispatch_source_set_timer().
You cannot remove or otherwise change an operation enqueued on a dispatch queue. Try using the higher level NSOperationQueue instead which supports cancellation.

AFNetworking - Serialize Connection

I use AFNetworking as my connection lib to my app. Due to the back-end restrictions, I cannot send two request simultaneously when the app starts because the server will identify a CookieTheftException (Grails). After a first successful connection, I can do as many simultaneous requests as I want but the first need to be serial.
How can I achieve that?
I thought using a Semaphore but i can't block the main thread.
Edit 1
I tried to override but it didn't work. I think the operation queue doesn't wait one request to finish (including it's callback) to start the other.
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation
{
[self.operationQueue setMaxConcurrentOperationCount:1];
[super enqueueHTTPRequestOperation:operation];
}
Edit 2
I realized that the maxConcurrentOperation worked and in fact 1 operation is executed at a time. The problem is that the request enqueued is already created without the cookies the server needs.
I don't know anything about Grails or the specific architecture of your system, but perhaps this could be solved by simply turning off cookies on that request, with NSMutableURLRequest -setHTTPShouldHandleCookies:.
Other than that, the best way to ensure that only one request operation is ever running for that initial call would be to ignore queues altogether, and simply have an AFHTTPRequestOperation property on your AFHTTPClient subclass. You could even get fancy with KVO to ensure that the operation queue is suspended until that initial request is finished.
I would recommend to read about GCD
You can create a queue and put some block to be executed in this queue.
This way:
It won't block main thread
Since all your networking blocks will be executed in one queue, there is no way that two blocks will be executed simultaneously.
You could set the maximum concurrent operations of the queue to 1. That way only one request will be made at a time.
[self.httpClient.operationQueue setMaxConcurrentOperationCount:1];
But since you only need to wait for the first request, why not just call that request on its own, then initialize the other requests only when the first one completed?

Resources