user2993422
user2993422

Reputation: 306

problems using Grand Central Dispatch IOS Swift Xcode 6.3.1

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

Answers (2)

Duncan C
Duncan C

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.

EDIT:

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:

  • Make it not return anything. (The result won't be available until after the async block completes anyway.)
  • Make requestNewBroadcasts take a completion block (or rather a closure, as they are called in Swift). Get rid of the TableViewController.requestFinished(broadcasts) call entirely, and instead have your network code call the completion block once the network request is complete.
  • Make your call to dispatch_async use a background queue rather than the main queue so that your task actually runs in the background.
  • Make your requestNewBroadcasts method invoke the completion block on the main thread.

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

Eric Aya
Eric Aya

Reputation: 70098

return broadcasts is executed before the loop that appends data to your array because of DataManager.getBroadcastsFromTweeterWithSuccess's closure.

Upvotes: 1

Related Questions