Designing a News Reader iOS app in Swift – Part 2

In this blog post, we’ll be focusing on the main architectural components of our news reader iOS app. We’ve discussed the most important features of a news reading mobile app as well as the best design practices in the first part of this series.

designing news reader ios app swift-part-2

The general idea is to separate the concerns as much as possible. In some cases, identifying concerns can be tricky, since they are not that obvious. As a guideline, the code that is unit-testable (without hideous mocks and stubs) is usually well designed. Another rule of thumb is that if you need to expose private properties or methods for testing, there are flaws in the architecture.

Modeling the News Reader data

We recommend you to start with the models since they’re the components that are the most quickly to identify. The models reflect a concrete approach of modeling your app’s data. You’ll probably have a model for each table of your database.

For our news reader app, identifying the models is pretty straightforward:

  • User – this entity encapsulates user’s data (email, auth tokens, first name, last name, device id)
  • News – one article is described by title, content, photo, author, category, publish date, etc
  • Author – the person/user who wrote the article (first name, last name) – you might want to make this a User if your users also post articles
  • Category – which is probably described by category id, name, etc (e.q. “Technology”)

Usually, in Swift, you’ll want your models to be immutable structs. This guarantees thread-safety, consistency and the assurance for the consuming objects that no one will change the models they’re relying on. This might not be possible with some third-party libraries (like ObjectMapper), even if it’s highly recommended.

Since our models will be created from JSON data received from the server, we’ll quickly realize that for each model there’s the need of an initializer that maps the JSON dictionary fields to the actual properties. Because we only want a single component for retrieving data from our endpoints, we’ll abstract out the JSON initializer via a protocol named “JSONParsable“. The component that will be in charge of fetching models from the server (such as news), we’ll only fetch JSONParsable objects, so it won’t need any other details about the models that are being fetched. In this way, we decouple our models from the component fetching the news from the server.

Here’s a glimpse of the News model definition:

protocol JSONParsable {
    init(dict: [String: String])
}

struct News: JSONParsable {
    fileprivate let id: String
    fileprivate let title: String
    fileprivate let content: String
    fileprivate let photoURLString: String

    init(dict: [String: String]) {
        id = dict["id"]!
        title = dict["title"]!
        content = dict["content"]!
        photoURLString = dict["photo_url"]!
    }
}

Making networking requests from the News Reader

A News Reader iOS app communicates with our server via network requests. As we stated in the first blog post of this series, our articles also have photos, which will be displayed in our user interface. Downloading those images from our News Reader app will also require network calls.

In addition to that, let’s assume you want to write unit tests for fetching the articles from the server. It’d be a nightmare if those unit tests will actually make real network requests when running.

If the two tips above are not obvious enough, let’s also assume that at some point in your company’s life, someone will need to measure the latency of all the network requests across the entire news reader app. What would be the wisest design decision in this case?

The three hints above prefigure the need of abstracting out the network layer. This brings in a multitude of benefits, such as robustness, flexibility, and scalability. We’ll inject a networking manager into all the components that require network access.

We highly recommend using Alamofire to implement the networking interactions. In that case, a networking component implementation will get as easy as this one:

enum NetworkResponseStatus {
    case success
    case error(string: String?)
}

class NetworkingManager {
    func get(path: String, params: [String:String]?, completion: @escaping ((_ jsonResponse: Any?, _ responseStatus: ATCNetworkResponseStatus) -> Void)) {

        Alamofire.request(path, parameters: params).responseJSON { response in
            if let json = response.result.value {
                completion(json, .success)
            } else {
                completion(nil, .error(string: response.result.error?.localizedDescription))
            }
        }
    }
}

Notice that we abstracted out the networking response status in the NetworkResponseStatus enum. In this way, the consumers of the networking component will be able to handle potential errors on their own. If you want a more granular error handling, you could send the actual NSError object, rather than the localized description. Moreover, we left the post method implementation as an exercise for the reader.

Interacting with the News Reader backend endpoints

We need a component that mediates the interaction between our iOS client and the server. Let’s call it API Manager. As you can probably imagine, this manager will be in charge with fetching/updating data from/on our server. The server exposes a RESTful API, whose endpoints can be queried by our iOS News Reader app. If we have multiple endpoints, the API Manager will have multiple methods. Because network requests have significant latency, all the calls to the endpoints need to be asynchronous. In order to notify the consumers that the request came back, the API manager’s methods will take a closure as one of the parameters (completionBlock) that will be called once the response is back. Since the methods of the API Manager are very specific (“fetchNews” for instance), they will take care of processing the backend responses as well.

It’s easy to fall into the trap of making the API Manager a singleton. We only have one backend, after all, right? Well, if you chose this approach, good luck unit testing! It’s usually better to inject the API Manager as a dependency to your view controllers rather than relying on a single global object. In this way, every test and every view controller will have their own instance of the API Manager, so there’s less room for bugs and no extra clean up work is required.

Building on the dependency injection pattern, it’s safe to predict that we’ll also inject a network manager into the API Manager. In this way, when writing the tests for the API Manager, we can just use a fake networking component so that the tests won’t make real network requests. You got the point, right? A possible implementation of this API Manager that encapsulates the news endpoint looks like the following:

class EndpointConstants {
    static let kNewsEndpoint = "http://iosapptemplates.com/posts.json"
}

class APIManager {

    fileprivate let networkingManager: NetworkingManager

    public required init(networkingMananger: NetworkingManager) {
        self.networkingManager = networkingManager
    }

    func retrieveNews(completion: @escaping (_ objects: [News]?, _ status: NetworkResponseStatus) -> Void) {
        self.retrieveListFromJSON(path: EndpointConstants.kNewsEndpoint, parameters: [:], completion: completion)
    }

    fileprivate func retrieveListFromJSON(path: String, parameters: [String:String]?, completion: @escaping (_ objects: [T]?, _ status: NetworkResponseStatus) -> Void) {
        networkingMananger.getJSONResponse(path: path, parameters: parameters) { (jsonData: Any?, status: NetworkResponseStatus) in
            if let jsonArray = jsonData as? [[String: Any]] {
                completion(jsonArray.flatMap{T(dict: $0 as! [String : String])}, status)
            } else {
                completion(nil, status)
            }
        }
    }
}

Notice how “retrieveListFromJSON” takes a class conforming to JSONParsable and instantiates objects of that class, passing them back through the completion block. You can use this method for all the other endpoints that return JSON arrays of models.

Displaying articles in the News Reader iOS app

We’ve described the components that are fetching & parsing the JSON data from our backend. Now it’s time to render it. For this, we’ll use a collection view controller. We recommend you to avoid using a table view controller since practically it has no advantages over the collection view controller.

We want to display news, so the backing objects for the collection view data source will be an array of these articles, previously fetched by an API Manager. This manager will be injected into the view controller’s constructors, to facilitate unit testing.

As we stated before, we want an MVVM architecture, so backing the data source by an array of News objects will break that. If for some reason, we’ll want to reuse the collection view controller to display blog posts for example, rather than News, we won’t be able to reuse too much code.

Instead of using an array of News, as the data source, we will use an array of objects that conform to a protocol. We proceed in the same manner we did for the model parsing. We’ll create an ArticleDisplayable protocol and have all our models conform to it. The collection view controller will now be backed by an array of ArticleDisplayable objects and all the models that we want to render using this view controller will conform to the protocol (News, BlogPost, etc). The custom UICollectionViewCell views will also be configured through an ArticleDisplayble object. In this case, we separated the concerns of providing the data for our views (view models) and modeling the data (models). This decouples our view controllers from the models, which is a better design. We’ll leave the implementation of this last tweak as an exercise for the reader.

What other design practices would you recommend for a news reader app? Don’t hesitate to send us feedback if you spot something that doesn’t feel right.

 

One thought on “Designing a News Reader iOS app in Swift – Part 2

  1. Great article and good explanation. But this really begs the question: what do the viewmodels look like and the controllers? Hoping you will post a part 3 with more code samples or even a sample demo app download on github that really breaks down all the pieces. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *