Reputation: 306
I'm learning swift now and having hard time understanding the multithreading issues..
specific problem Im having is that i'm loading data from the internet and trying to return an array ("broadcasts") containing this data while using dispatch_async .
My problem is that the return execution with the empty array happens before the array is filled with the data (this line "println(broadcasts)" happens but the array returns empty.. )
Here is my code:
import UIKit
public class BroadcastRequest {
func requestNewBroadcasts() -> [BroadcastModel] {
var broadcasts = [BroadcastModel]()
var sectionsOfBroadcasts = [[BroadcastModel]]()
DataManager.getBroadcastsFrom׳TweeterWithSuccess { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json = JSON(data: youTubeData)
if let broadcastsArray = json["result"].array {
for broadcastDict in broadcastsArray{
var broadcastID: String? = broadcastDict["id"].string
var broadcastURL: String? = broadcastDict["broadcast"].string
DataManager.getBroadcastDetailsFromYouTubeWithSuccess(broadcastURL!) { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json2 = JSON(data: youTubeData)
if let broadcastDetailsDict = json2["result"].dictionary {
var cover: String! = broadcastDetailsDict["cover"]!.string
var caption: String! = broadcastDetailsDict["caption"]!.string
var broadcast = BroadcastModel(id: broadcastID, broadcastURL: broadcastURL, cover: cover, caption: caption)
broadcasts.append(broadcast)
**println(broadcasts)**
}
}
}
}
}
}
}
**return broadcasts**
}
}
After looking at answers i have changed the code to this:
import UIKit
public class BroadcastRequest {
func requestNewBroadcasts() {
var broadcasts = [BroadcastModel]()
var sectionsOfBroadcasts = [[BroadcastModel]]()
DataManager.getBroadcastsFrom׳TweeterWithSuccess { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json = JSON(data: youTubeData)
if let broadcastsArray = json["result"].array {
for broadcastDict in broadcastsArray{
var broadcastID: String? = broadcastDict["id"].string
var broadcastURL: String? = broadcastDict["broadcast"].string
DataManager.getBroadcastDetailsFromYouTubeWithSuccess(broadcastURL!) { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json2 = JSON(data: youTubeData)
if let broadcastDetailsDict = json2["result"].dictionary {
var cover: String! = broadcastDetailsDict["cover"]!.string
var caption: String! = broadcastDetailsDict["caption"]!.string
var broadcast = BroadcastModel(id: broadcastID, broadcastURL: broadcastURL, cover: cover, caption: caption)
broadcasts.append(broadcast)
println(broadcasts)
}
}
}
}
}
}
}
**TableViewController.requestFinished(broadcasts)**
}
}
And in class TableViewController-
public class TableViewController: UITableViewController {
...
...
public func requestFinished(requestedBroadcasts: [BroadcastModel]) {
self.broadcasts = requestedBroadcasts
self.tableView.reloadData()
}
Now i get the error: Cannot invoke 'requestFinished' with an argument list of type '([(BroadcastModel)])'
Upvotes: 0
Views: 435
Reputation: 131418
Your code is wrong. A call to a GCD function like dispatch_async returns immediately, before the code in the closure has even begun executing. That's the nature of concurrent programming.
You can't write async code that makes a call and then has the answer on the next line. Instead you need to change your logic so that you make the call to dispatch async and then return and forget about the request until the block completes. Then you put the code that handles the result inside the closure. You can add a call inside your closure that calls out to your app on the main thread to notify it that processing is complete.
Your new code has multiple problems:
Your call to dispatch_async is using the main queue, which is a queue on the main thread. If your goal is to get this code to run in the background, this code fails to do that.
The call to TableViewController.requestFinished(broadcasts)
is still in the wrong place. It needs to be inside the block, after the data has been fetched. it also needs to be performed on the main thread.
The call to TableViewController.requestFinished(broadcasts)
appears to be sending a message to the TableViewController class rather than to an instance of the TableViewController class, which is wrong. You can't fix that unless your block has access to the instance of TableViewController that you want to send the message to.
You should probably refactor your requestNewBroadcasts method as follows:
TableViewController.requestFinished(broadcasts)
call entirely, and instead have your network code call the completion block once the network request is complete.Each of those steps is going to require work and research on your part.
Figuring out how to add a closure as a parameter will take digging. (See the Swift Programming Language iBook. It explains it well.)
Figuring out how to get a background queue will take work. (See the Xcode docs documentation on GCD. In particular look at dispatch_get_global_queue)
Figuring out how to make a call to the main thread from a background thread will take research (again see the Xcode docs on GCD. Hint: Your current call to dispatch_async is sending your block to a queue on the main thread.).
Upvotes: 2
Reputation: 70098
return broadcasts
is executed before the loop that appends data to your array because of DataManager.getBroadcastsFromTweeterWithSuccess
's closure.
Upvotes: 1