I have a question that might be not specifically about implementation but rather a tip/best practice thing.
I am working on a class in Swift that gets data from an online source in JSON format. I want to have specific methods in this class that connect to the online source and give the result back in a Dictionary type. The function might be like this:
func getListFromOnline()->[String:String]{
var resultList : [String:String]=[:]
...
/*
Some HTTP request is sent to the online source with NSURLRequest
Then I parse the result and assign it to the resultList
*/
...
return resultList
}
What I get right now in this implementation, without the multithreading, the resultList
is apparently returned before fetching from the online source is done. Not surprisingly, it leads to failure in the program.
Any idea or tip how I can achieve the opposite? I am a bit confused with multithreading in this instance, because I want to call this public method asynchronously later from another class and I know how to do that, but I don't get how to make the method that returns an argument have multithreading within itself.
Or is it not about multithreading at all and I am not seeing an obvious solution here?
There are three easy and completely expect approaches to this problem in Swift/Objective-C development, and neither of these solutions involve the method returning the value directly. You could write code that waited for the asynchronous part to complete (blocking a thread) and then return the value, and there are cases where this is done in some of Apple's own libraries, but I'm not going to cover the approach, because it's really not that great of an idea.
The first approach involves completion blocks.
When our method is going to perform some asynchronous code, we can pass in a block of code to execute whenever the asynchronous work is done. Such a method would look like this:
func asynchStuff(completionHandler: ([String:String]) -> Void) {
// do asynchronous stuff, building a [String:String]
let result: [String: String] = // the result we got async
completionHandler(result)
}
Remember to call completionHandler()
on the same thread that asynchStuff
was called on. (This example does not demonstrate that.)
The second approach involves delegates and protocols.
We need a class to do the asynchronous work. This class will hold a reference to our delegate, which will implement completion methods. First, a protocol:
@objc protocol AsyncDelegate {
func complete(result: [String:String]
}
Now our async worker:
class AsyncWorker {
weak var delegate: AsyncDelegate?
func doAsyncWork() {
// like before, do async work...
let result: [String: String] = // the result we got async
self.delegate?.complete(result)
}
}
Remember to be certain that we're calling the completion delegate method on the same thread that doAsyncWork()
was called on. (This example does not demonstrate that.)
The third approach can be done using NSNotificationCenter
. The times when this is the appropriate approach are going to be so rare that I'm not even going to bother with even a rudimentary example like I did the other two examples because you should almost certainly be using one of the first two examples in almost every scenario.
Which approach you use depends entirely on your specific use case. In Objective-C, I frequently preferred delegation to block based approach (although sometimes blocks are correct), but Swift allows us to pass regular functions/methods as our block argument so it leans me slightly back toward using the block-based approach for Swift, but still, use the right tool for the right job, as always.
I wanted to expand this answer to address calling the callback (whether blocks or delegates) on the appropriate thread. I'm not sure if there's a way to do this with GCD still, but we can do it with NSOperationQueue
s.
func asyncStuff(completionHandler: ([String:String]) -> Void) {
let currentQueue = NSOperationQueue.currentQueue()
someObject.doItsAsyncStuff(someArg, completion: {
result: [String:String] in
currentQueue.addOperationWithBlock() {
completionHandler(result)
}
}
}