4
\$\begingroup\$

I am new in iOS development and Swift. I am going to develop a simple application which communicates with external REST services. This is my first experiment.

I appreciate feedback on best practices and improvements. Style comments are always considered as well.

import Foundation import SwiftyJSON class UserObject { var pictureURL: String! var username: String! required init(json: JSON) { pictureURL = json["picture"]["medium"].stringValue username = json["email"].stringValue } } 
import Foundation import SwiftyJSON typealias ServiceResponse = (JSON, Error?) -> Void class RestApiManager: NSObject { static let sharedInstance = RestApiManager() let baseURL = "http://api.randomuser.me/" func getRandomUser(onCompletion: @escaping (JSON) -> Void) { let route = baseURL makeHTTPGetRequest(path: route, onCompletion: { json, err in onCompletion(json as JSON) }) } private func makeHTTPGetRequest(path: String, onCompletion: @escaping ServiceResponse) { let request = URLRequest(url: URL(string: path)!) let session = URLSession.shared session.dataTask(with: request) {data, response, err in if(err != nil) { onCompletion(JSON.null, err) } else { let jsonData = data let json:JSON = JSON(data: jsonData!) onCompletion(json, nil) } }.resume() } } 
import UIKit import SwiftyJSON class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { var tableView: UITableView! var items = [UserObject]() override func viewWillAppear(_ animated: Bool) { let frame:CGRect = CGRect(x: 0, y: 100, width: self.view.frame.width, height: self.view.frame.height - 100) self.tableView = UITableView(frame: frame) self.tableView.dataSource = self self.tableView.delegate = self self.view.addSubview(self.tableView) let btn = UIButton(frame: CGRect(x: 0, y: 25, width: self.view.frame.width, height: 50)) btn.backgroundColor = UIColor.cyan btn.setTitle("Add new dummy", for: UIControlState.normal) btn.addTarget(self, action: #selector(addDummyData), for: UIControlEvents.touchUpInside) self.view.addSubview(btn) } func addDummyData() { RestApiManager.sharedInstance.getRandomUser { (json: JSON) in if let results = json["results"].array { for entry in results { self.items.append(UserObject(json: entry)) } DispatchQueue.main.async { self.tableView.reloadData() } } } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCell(withIdentifier: "CELL") if cell == nil { cell = UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "CELL") } let user = self.items[indexPath.row] if let url = NSURL(string: user.pictureURL) { if let data = NSData(contentsOf: url as URL) { cell?.imageView?.image = UIImage(data: data as Data) } } cell!.textLabel?.text = user.username return cell! } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } 
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This is mostly a clean and well considered design. Some opportunities for improvement which stand out:

Make the baseURL property a parameter of the initialiser for RestApiManager. This will allow you to construct the api class with any URL, which is useful for testing, or initialising from a config file.

class RestApiManager: NSObject { static let sharedInstance = RestApiManager(baseURL: "http://api.randomuser.me/") let baseURL = "http://api.randomuser.me/" init(baseURL: URL) { self.baseURL = baseURL } 

RestApiManager.getRandomUser should return a UserObject instance, instead of a dictionary. The view controller should not need to know the intricacies of parsing the JSON object.

func getRandomUser(onCompletion: @escaping ([UserObject]) -> Void) { let route = baseURL makeHTTPGetRequest(path: route, onCompletion: { json, err in var users = [UserObject]() if let results = json["results"].array { for entry in results { users.append(UserObject(json: entry)) } } onCompletion(users) }) } 

The view controller should not depend on a concrete class. The API class should conform to a protocol, which is the dependency used in the view controller. This allows the view controller to be tested with a mock API.

protocol APIProtocol { func getRandomUser(completion: @escaping ([UserObject]) -> Void) } class RestApiManager: NSObject, APIProtocol { func getRandomUser(onCompletion: @escaping ([UserObject]) -> Void) { 

Use dependency inversion to pass in an instance of the api class to the view controller.

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { var api: APIProtocol! // ... more code func addDummyData() { api.getRandomUser { (users: [UserObject]) in DispatchQueue.main.async { // Update on main queue to prevent race condition // if multiple requests complete at the same time self.users.append(contentsOf: users) self.tableView.reloadData() } } } 

Usage:

let viewController = // instantiate view controller viewController.api = RestApiManager.sharedInstance 
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.