Design implementation for a RESTful JSON parser

Hi

Similar to my other question, I’m working at building essentially a web service to display data from my own JSON API. I feel that is a decent complexity to start of with learning swift and the iOS development. I’ve managed to get my json parser to get and read the data. Thanks to @narrativium for his help with checking over my stuff. Everything has been done through the viewDidLoad() method. I’ve been working my way through a Udemy course, which is good in principle, but coming from a programming background I feel it isn’t great separation of code? And the parser should become more flexible. I also used a tutorial here https://www.raywenderlich.com/113388/storyboards-tutorial-in-ios-9-part-1 on storyboards which was great. And I’m trying to change the Player.swift (Mine is called Game.swift) and PlayerData.swift (Mine is called SampleData) files from that tutorial to collected data from the json API. I’m still fumbling my way through iOS and Swift so wanted some guidance. This is how I’ve started to rearrange my code now:

I have:

GameController.swift (UIViewController + UITableViewDelegate) that lists some current game scores by pulling from my API
NewsController.swift (UIViewController + UITableViewDelegate) that lists the latest stories from my API

SampleData.swift from ray wenderlich tutorial

class Data: UIViewController {

let GameData = [
Game(timer: 30, home: "Man City", away: "Chelsea", score: "2 - 2", homeBadge: "city.png", awayBadge: "chelsea.png"),
Game(timer: 80, home: "Liverpool", away: "Man U", score: "1 - 2", homeBadge: "liverpool.png", awayBadge: "manu.png"),
   ]
}

** Game **

import UIKit

struct Game {
var timer: Int?
var home: String
var away: String
var score: String
var homeBadge: String
var awayBadge: String

init(timer: Int?, home: String, away: String, score: String, homeBadge: String, awayBadge: String) {
    self.timer = timer
    self.home = home
    self.away = away
    self.score = score
    self.awayBadge = awayBadge
    self.homeBadge = homeBadge
    
}

}

Api.swift my model for pulling the data.

import Foundation

class Api {
// api url string
let url = NSURL(string: "http://api.dev/web/game-view")!

func DataManager() -> [Dictionary<String, String>] {
    // start task manager
    let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
        
    // init array of dictionaries
    var gameData = [Dictionary<String, String>]()
    
    // gracefully continue if no data
    guard let urlContent = data
        else { return }
    do {
        // create json object
        let jsonObject = try NSJSONSerialization.JSONObjectWithData(urlContent, options: .MutableContainers)
        
        // down cast types
        guard let jsonArray = jsonObject as? [[String: AnyObject]]
            else { return }
        
        // loop through jsonArray and extract values where possible
        for dictionary in jsonArray {
            // if home & away scores exist a "score" can be created
            if let home_score = dictionary["home_score"] as? Int,
                away_score = dictionary["away_score"] as? Int {
                
                    let score = ["score": "\(home_score) - \(away_score)"]
                    gameData.append(score)
                
                    return gameData // Error: unexpected non-void return value in void function
            }
        }
    } catch {
        print("Error")
    }
}

// resume task regardless
task.resume()
}
}

I’m not quite sure of a couple things here:

1 - What’s the way to return data from my DataManager() method in the Api.swift? I’m assuming the error is because it’s inside the completion handler, but don’t know how to accomplish that.
2 - In terms of design implementation how should I be going about converting the code from tutorial, do I need to keep my Game.swift struct?

  1. You don’t return data from that method. The method does exactly what it says in the first comment: it starts a task manager. It defines ‘task’ to download data, and it ‘resumes’ that task, and then it returns. That means it returns before your download finishes. That’s important: you don’t want the code waiting until the download finishes, you want your view controller to appear on screen, you want the user to be able to press buttons and stuff.

Almost certainly, task.resume() happens and the method ends before var gameData = [Dictionary<String, String>]() happens. The completion handler is a closure which gets called once the download finishes. Whatever called DataManager() needs to work on the basis that it can tell the download to start, but it can’t expect instant results, and the system should carry on working without receiving data (especially if there’s a problem, e.g. the device is offline, loses signal, is in airplane mode, whatever). Instead, you need a function which should be called when the download is complete, to change that system into one with data, and you should call that function in the closure.

This is ‘asynchronous’ or ‘event-driven’ programming. It takes some getting used to.

  1. I couldn’t say. It’s your game, I don’t know what you’ll need. If you’re interested in app architecture, the RW site has some tutorials on design patterns, Model-View-Controller, Model-View-ViewModel, which might offer you a starting point. The objc.io website has an issue dedicated to app architecture, which you might find interesting reading. Separation of code - for example, ensuring that each type only has a single responsibility, or using protocols to break dependencies between different types - is worthwhile learning.
1 Like