Search code examples
iosswiftuitableviewasynchronousalamofire

Working with Model async data and TableView


I'm creating an app and I have all the logic done, but I want to do a Code refactoring and create MVC pattern. But I dealing with some asynchronous informations, that came from API.

/MenuViewController

Alamofire.request(.GET, Urls.menu).responseJSON { request in
            if let json = request.result.value {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
                    let data = JSON(json)
                    var product: [Product] = []

                    for (_, subJson): (String, JSON) in data {
                        product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
                    }

                    dispatch_async(dispatch_get_main_queue()) {
                        self.products += product
                        self.tableView.reloadData()
                    }
                }
            }
        }

This is my code, already working. But I want to create a Model that will handle this and just return the array of Products to my MenuViewController.

Model/Menu

class Menu {
    var products: [Product] = []

    init() {
        Alamofire.request(.GET, Urls.menu).responseJSON { request in
            if let json = request.result.value {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
                    let data = JSON(json)
                    var product: [Product] = []

                    for (_, subJson): (String, JSON) in data {
                         product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
                    }

                    dispatch_async(dispatch_get_main_queue()) {
                        self.products += product
                    }
                }
            }
        }
    }

    func totalOfProducts() -> Int {
        return self.products.count
    }

    func getProducts() -> [Product]? {
        return self.products
    }

    func getProductFromIndex(index: Int) -> Product {
        return self.products[index]
    }
}

But I got my self thinking, how I gonna get the main_queue to another class?

So I tried something like this:

class MenuViewControlvar: UITableViewController {

    var products: [Product] = []
    let menu: Menu = Menu()

    // MARK: View Controller Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        if let products = menu.getProducts() {
            self.tableView.reloadData()
        }

        // rest of the code

But didn't worked. My TableView is never updated.

I was wondering if I can do this, or I've to keep my Alamofire code in my viewDidLoad() from my MenuViewController

Thank you.


Solution

  • Delegation is an ideal solution for this problem of updating your model data and your view based on an asynchronous network call and it’s pretty much the same technique that is implemented throughout the iOS SDK to solve the same problem. There are many benefits of delegation over observation, another viable solution.

    First, move your networking code to a separate class

    class NetworkingController { 
    

    Create a protocol that view controllers can conform to. This provides the loose coupling between your network operations and your views to effectively maintain separation between the MVC layers.

    @protocol NetworkingControllerDelegate: class {    
        func menuDataDidUpdate()
    }
    

    Have the networking controller support a property for its delegate

    weak var delegate: NetworkingControllerDelegate?
    

    In summary your networking class now looks something like this:

    @protocol NetworkingControllerDelegate: class {    
        func menuDataDidUpdate()
    }
    
    class NetworkingController { 
        weak var delegate: NetworkingControllerDelegate?
    
        // Insert networking functions here.
    }
    

    Then, have your view controller conform to this protocol like so

    class MenuViewController: NetworkingControllerDelegate {
    

    and create a new network controller in your view controller

    var myNetworkController = NetworkController()
    

    and set the delegate of your network controller instance to be your view controller

    myNetworkController.delegate = self
    

    Then in your networking code, when the network request has completed and your model has been updated, make a call to the networking controller's delegate.

    delegate.menuDidUpdate()
    

    Create the implementation for this method in your view controller since it is now the delegate for your networking code.

    func menuDidUpdate() { 
        // Update your menu.
    }
    

    This makes your view controller look something like:

    class MenuViewController: NetworkingControllerDelegate {
        var myNetworkController = NetworkController()
    
        override func viewDidLoad() {
            myNetworkController.delegate = self
        }
    
        // MARK: NetworkingControllerDelegate
    
        func menuDidUpdate() { 
            // Update your menu.
        }
    }
    

    This is just the outline of the implementation to give you the necessary information about how to proceed. Fully adapting this to your problem is up to you.