3
\$\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.
 }
}
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Mar 17, 2017 at 11:26
\$\endgroup\$

1 Answer 1

1
\$\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
answered Apr 15, 2017 at 12:40
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.