iOS: Force to cancel Operation in NSOperationQueue - ios

I need to cancel all the operations in the NSOperationQueue immediately.
Consider my scenario,
I have to hit my server continuously ie I will be calling my server whenever user types in the textbox. After user completes his input I have to hit final api call. So I create one NSOperation for single API hit. While user types in the textbox. I create NSOperation object and add that in NSOperationQueue. After detecting that user completely his input, I cancelled all the operation in the queue and hit my final api. The Problem is some operations are not cancelled immediately. So my final api hit is not called immediately. It is waiting for some time (all operation have to finish) and then it called.
FYI,
myOperationQueue.cancelAllOperations()
In Operation start method I have this code
let session = URLSession.shared
let task = session.dataTask(with: urlRequest, completionHandler: { (data, urlResponse, error) -> Void in
})
task.resume()
Please provide me the best way to call my final API immediately.

The apple documentation about the cancellAllOperations method, it clearly explains your situation.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state. For operations that are already executing, the operation object itself must check for cancellation and stop what it is doing so that it can move to the finished state. In both cases, a finished (or canceled) operation is still given a chance to execute its completion block before it is removed from the queue.

You have to cancel your task as well.
Implement a cancel override in your Operation subclass. This override cancels the task as well.
var task: URLSessionTask?
func scheduleTask() {
let session = URLSession.shared
///Task is an instance variable
task = session.dataTask(with: urlRequest, completionHandler: { (data, urlResponse, error) -> Void in
})
task?.resume()
}
public override func cancel() {
task?.cancel()
super.cancel()
}

Related

Dispatch Group not allowing Alamofire request to execute

I'm using DispatchGroup to wait until a callback for one of my functions executes before continuing. Within that function, I'm calling Alamo fire get request. My issue occurs when I introduce the DispatchGroup, the AlamoFire closure never gets executed.
Sample
let group = DispatchGroup()
group.enter()
Networking.getInfo(userID: userID) { info in
group.leave()
}
group.wait()
Networking class:
static func getInfo(userID: Int, completion: #escaping(_ info: String) -> Void) {
// Program reaches here
Alamofire.request("https://someurl.com").responseJSON { response in
// Program does NOT get here
if let json = response.result.value {
completion("Successful request")
} else {
completion("Some Error")
}
}
}
When I don't use the DispatchGroup, it works fine. When I DO use the DispatchGroup, The getInfo function starts, but the closure of the Alamofire request never gets executed.
Not sure I'm right, but I suspect that the Alamofire response is being queued on the same queue (main) that the group has suspended (wait()). Because the queue is suspended, the completion closure never executes.
Manually writing asynchronous code like this can be quite tricky. My suggestion would be to use any one of the asynchronous libraries out there which can help with this. My personal favourite being PromiseKit which also has specific extensions to support Alamofire. Projects like this can take a lot of the headache out of asynchronous code. They may take some time to get your head around their paradigms, but it's worth doing.
I faced the same problem. In that case I used URLSession request to get ride of it. This API enables your app to perform background downloads when your app isn’t running or, in iOS, while your app is suspended.
https://developer.apple.com/documentation/foundation/urlsession
let request = try URLRequest(url: url, method: .get, headers: headers)
Alamofire.request(request) { response in
...
...
}
I changed it like this :-
let request = try URLRequest(url: url, method: .get, headers: headers)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
...
...
}
task.resume()
And then it worked fine.

Get the latest result from DispatchGroup wait

Problem Desctiption:
I want to do a bunch of asynchronous tasks by 'DispatchGroup' and when all of them finished it returned the result. In addition, I want to set timeout that limits the process and send me back the successful results by that time. I used the following structure:
Code Block
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
// Timeout for 10 seconds
myGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
myGroup.notify(queue: .main) {
return result
}
How can I get the latest result if timeout happened?
Ok, so you are correctly using the enter/leave functionality of the DispatchGroup, but are having trouble with how to access the results of these. I think you are going wrong by trying to use both wait and notify, these two functions provide two different pieces of functionality not usually used together. After having setup up your work items, as you have done, you have two options:
The wait approach
This function blocks the calling queue and wait synchronously for either, the passed in wall time to elapse, or all work items in the group to leave. Because it is blocking the caller, it is important to always have a timeout in this function.
The notify approach
The function takes a target queue, and a block to be run when all work items in your group have completed. Here you are basically asking the system to notify you, asynchronously once all work items have been completed. Since this is asynchronous we are usually less worried about the timeout, it's not blocking anything.
Asynchronous wait (this appears to be what you want?)
If, as it seems you do, we want to be notified once all work items are complete, but also have a timeout, we have to do this ourselves, and it's not all that tricky. We can add a simple extension for the DispatchGroup class...
extension DispatchGroup {
func notifyWait(target: DispatchQueue, timeout: DispatchTime, handler: #escaping (() -> Void)) {
DispatchQueue.global(qos: .default).async {
_ = self.wait(timeout: timeout)
target.async {
handler()
}
}
}
}
This simple function dispatches asynchronously on a global background queue, then calls wait, which will wait for all work items to complete, or the specified timeout, whichever comes first. Then it will call back to your handler on the specified queue.
So that's the theory, how can you use this. We can keep your initial setup exactly the same
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
and then use our new function to wait for the end
myGroup.notifyWait(target: .main,
timeout: DispatchTime.now() + 10) {
// here you can access the `results` list, with any data that has
// been appended by the work items above before the timeout
// was reached
}

Completion block method vs. DispatchQueue

I have implemented following completion block, one block is completed and then I update UI and object accordingly.
func doPaging() {
fetchProducts(page: pageNumber , completion: { success in
if let products = success as? Products
{
DispatchQueue.main.async {
self.products.append(contentsOf:products)
self.isWating = false;
self.productTableView.reloadData()
}
}
})
}
func fetchProducts(page: Int, completion: #escaping ((AnyObject) -> Void)) {
// URLSession call here
}
However, the following approach clearly shows restful call will happen in background thread and once it is completed, then update UI and objects.
func doPaging() {
DispatchQueue.global(qos: .background).async {
// Background Thread
fetchProducts()
DispatchQueue.main.async {
self.pageNumber += 1
self.productTableView.reloadData()
self.isWating = false
}
}
}
func fetchProducts(page: Int) {
// URLSession call here
}
I am confused between completion block method vs. DispatchQueue.
Which one is recommended?
In the first approach, you call a method fetchProducts() which internally uses NSURLSession. REST call using NSURLSession runs in background and on completion of the REST call, the completion of the task will be called. In that completion, you call your completion handler of fetchProducts(). This approach seems fine to me.
In the second approach, you use global background queue and asynchronously call NSURLSession APIs (I assume so), and don’t wait for the call to complete. The code on main queue will be instantly called and at this point the NSURLSession task may or may not have been completed.
So, this approach is problematic.
First method seems OK as long as you fetchProducts asynchronously. In fetchProducts() , if you call the completion block in the main queue you won't even need to get main queue again in the doPaging() method.
In your second method, you are calling fetchProducts() in a global (concurrent) queue. Although global queues start each task in the order they were added to queue, they run tasks concurrently. And since fechtProduct() takes time, your code block that contains self.pageNumber += 1 executed before even fetchProduct's URLSession is started. So, this approach won't work.
Completion block and Dispatch Queue are two different concepts.
Completion block is used when your function perform actions takes time to run, and need to return back and run some code even the functions has "ended". For example,
func networkCall(foo: Int, completion:#escaping (_ result:Bool)-> Void))
func otherFunc(){...}
func A(){
networkCall(foo:1){ (success) in
// handle your stuff
}
otherFunc()
}
When you run A(), it first run networkCall(), however networkCall() may takes time to run the network request and the app moved on to run otherFunc(). When the network request is done, networkCall() can call it's completion block so that A() can handle it again.
Dispatch Queue is the threading stuff safely encapsulated by Apple. Network request can be performed in Main thread as well, but it will be blocking other functions.
A common practice is to call Network request in background queue
DispatchQueue.global(qos: .background).async and call completion block after finished. If anything needs to be updated in main thread like UI, do it in the DispatchQueue.main.async

Wait for Firebase to load before returning from a function

I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from #Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: #escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:#escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Making Duncan answer more precise. You can make the function like this
func loadFromFireBase(completionHandler:#escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
You can return the songArray in a completion handler block.

How to point to a task with it's own completions closure ? ios swift

I am sending request using tasks, in this way :
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
dispatch_async(dispatch_get_main_queue(), {
//remove from queue
weakself?.popTask(task: task)
}
}
self.pushTask(task)
task.resume()
i need to push the task into a queue before resume. then i need to pop it after it's finished. the problem that i got this error : variable used within it's own initial values
what should i do?
is it possible to create a task, then to set it's completion handler ?
Thanks
I think you're correct that the completion block can't refer to task because it hasn't been created yet.
That is what is meant by:
variable used within it's own initial values
To get around this, simply declare the constant before you set it. e.g.
let task: NSURLSession
task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
dispatch_async(dispatch_get_main_queue(), {
//remove from queue
weakself?.popTask(task: task)
}

Resources