Leveraging Structs and Generics in the Networking Layer with Swift 4 (An update to objc.io Swift Talk)

Swift Talk #1 and #8 introduces an approach (hereinafter alternative approach) to using structs and generics to build a networking layer in Foundation.

The original code was written in Swift 2.2. Here, I will make an update for Swift 4, incorporating the use of newly available JSON parsing features. I will also show how it could be extended to fetch images.

The Swift Talk Approach

Let's say you have a simple JSON response:

{
    "name": "Metal Toad"
}

You can represent this response in a Swift struct:

struct Company: Decodable {
    let name: String
}

With the alternative approach, making a network call is as easy as:

let resource = Resource<Company>(method: .get, url: <URL>)
Networking().load(resource: resource) { response in 
    guard let name = response?.name else { return }
    print(name)  // "Metal Toad"
}

First, we create a resource object that is type specific to the Company struct specifying the HTTP method and url.

Second, we initialize our Networking class and call load with the resource as the parameter.

Just like magic we receive a response of type Company that was specified when constructing our resource object.

One of the benefits to this approach is a guarantee that the response will be of type Company. Also, the response object is provided in a completion closure next to the place where the method is called, thereby improving locality of reasoning.

JSON Decoding before Swift 4

Network responses return static byte buffers encapsulated by the Data struct (or NSData if using reference semantics). Let's discuss how we convert the byte buffer into our Company struct.

Before Swift 4, we used JSONSerialization.jsonObject to convert Data into an object of type [String: AnyObject].

//static byte buffer from a network request
let data: Data
let json: [String: AnyObject] = try? JSONSerialization.jsonObject(with: data, options: [])

Next, we need a function to look at values in the JSON object and mapping them to the response object. It checks whether the JSON has the expected keys, and whether values conform to their expected types in the response object.

extension Company {
    func parseJSON(dictionary: [String: AnyObject]) {
        guard let id = dictionary["name"] as? String else { return nil }
        self.name = name
    }
}

Enter Swift 4's JSONDecoder

Swift 4 introduces a new interface for JSON decoding. We no longer need a parseJSON function to process [String: AnyObject]. Instead, we can directly go from Data to our target object, in this case Company.

Before we had to define the parse function of the Resource struct on a case by case basis, specifying the steps needed to transform Data into the response object.

struct Resource<A> {
    let url: URL
    let parse: (Data) -> A?
}

With Swift 4, we no longer need to define a parse function on a case by case basis. Instead, we can statically define it generically over any return type that conforms to Decodable in our Resource object.

Let's take a look at JSONDecoder().decode(_:from:):

func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable

This method takes Data as an argument and is generic over the return type, as long as the return type conforms to the protocol Decodable.

struct Resource<A> where A: Decodable {
    let url: URL
    let parse: (Data) -> A? = { data in return try? JSONDecoder().decode(A.self, from: data) }
}

Therefore, the use of this method is perfect for integrating into the Resource object of the alternative approach, since it is also generic over the return type. Another benefit is a check that the model we are asking for comforms to Decodable, otherwise it will fail at compile time.

Image Resource

The alternative approach can also be extended to support the fetching of image resources. Like JSON resources above, image data comes back as static byte buffers in Data. To make it useful we simply need to use UIImage.init?(data: Data). Therefore we define a new ImageResource object.

struct ImageResource {
    let url: URL
    let method: HttpMethod<Data>
    let parse: (Data) -> UIImage?
}
 
extension ImageResource {
    init(imageUrl: URL) {
        self.url = imageUrl
        self.method = .get
        self.parse = { data in return UIImage(data: data) }
    }
}

ImageResource now holds everything we need to fetch and parse data into a useful instance of UIImage.

Playground

Please see this gist for a Swift playground where we make networking calls to fetch a JSON resource, to post a request with a JSON body, and fetch a simple image resource.

Conclusion

Swift 4's new JSON processing features enhances the alternative approach by removing the requirement of specifying a custom JSON parsing method for each custom type, as long as it conforms to Decodable as required by JSONDecoder. Furthermore, the alternative approach is flexible enough to be extended to other types of network calls, like the fetching of images.

This is my own personal exploration into this alternative approach. Suggestions for improvements, especially in how this approach can be further optimized, is welcomed.

Comments

Great updated to the Swift Talk. Thanks for posting!

in this age of technology; convenience and automation, working with JSON for Swift shouldn't be complicated anymore. Without dependency on other tools and framework, this free online utility maps your web service responses to appropriate models. It makes working with JSON easier and manageable.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

About the Author

Phil Tseng, Software Engineer

Living by the adage: "You can do anything with a law degree," and decades of experience providing tech support to family and friends, Phil Tseng changed his career from Attorney to Software Engineer and received a degree in Computer Science from Portland State University in 2015. Phil is looking forward to refining his craft at Metal Toad.

Outside of work, Phil enjoys downhill skateboarding during the warmer months, downhill skiing during the colder ones, and yoga all year round to keep him zen and centered.

Ready for transformation?