Generics

Note: this post was written with Swift 1.2 and XCode 6.1

When writing a function in any programming language, it is commonplace to want the function to be versatile enough to accept several of different types as input parameters. For example you may want your “stringify” function to accept CGFloat, NSArray and User.

This would be a difficult ask for an Objective-C developer. Swift, however, gives us tools to write versatile functions and constrain them with our own type requirements. Whether we are parsing JSON, writing a mapping function, or generalizing a utility method, the core features of generics are there to make things easier for us.

Objective-C vs. Swift

iOS developers who prefer working with Objective-C might get a little squirmy at all this “generics” talk. After all, generics don’t have a place in the Objective-C universe. The Objective-C runtime does not care about an object’s type. When you append an object to an NSMutableArray, it is effectively of type id. This means that you risk triggering a runtime exception whenever you send a message to your array. Although this translates to handy versatility when it comes to manipulating built-in collections like NSArray and NSDictionary, it comes at a cost, especially when you are doing things like serializing an API response where the types you receive from the API with might not be what they were yesterday.

Generics

We can illustrate the versatility and type safety that Swift’s generics provide with an example:

Let’s say that we make a network call to an API, and we want to serialize the response into model objects. Our model object in this case is Car.

class Car: NSObject {
  var make:String?
  var model:String?  
}

We can’t be sure exactly what the API will return to us, so we make use of Swift’s AnyObject type to stand in for the response. We might parse the response like this:

private func carsFromResponseObject(response:AnyObject?) -> Car? {
    var car:Car?
  if let responseDictionary = response as? Dictionary<String:AnyObject> {
    if let model:String = responseDictionary["model"] as? String {
      if let make:String = responseDictionary["make"] as? String {
        car = Car(make:make, model:model)
      }
    }
  }

    return car
}

First, we have a check to see if the response can be cast to a Dictionary type with keys of type string. Then we nest several checks for additional keys, and if all the keys are present, we set a value to car and return it. Otherwise, we never set a value to car and return nil. This is a simple example, but imagine if we needed to check for 20 different keys - our carsFromResponseObject could would become deeply nested and difficult to read.

We can clean this up a little to read as follows:

private func carsFromResponseObject(response:AnyObject?) -> Car? {
    var car:Car?
  if let responseDictionary = response as? Dictionary<String:AnyObject>,
    model:String = responseDictionary["model"] as? String,
    make:String = responseDictionary["make"] as? String {
      car = Car(make:make, model:model)
  }
  
  return car
}

This reads a little better, but we want something more general. What we want, ultimately, is a function that can take a Dictionary as input and serialize it into a model object of any type.

First, let’s create a protocol for our model objects:

protocol JSONSerializable {
    init(responseDictionary:Dictionary<String, AnyObject>)
}

Now, let’s refactor our parsing code:

private func carsFromResponseObject(response:AnyObject?) -> Car? {
    var car:Car?
  if let responseDictionary = response as? Dictionary<String:AnyObject>
    car = modelFromResponseDictionary(responseDictionary)
  } 
  
  return car
}

private func modelFromResponseDictionary<T: JSONSerializable>(responseDictionary: [String: AnyObject]) -> T {
  let modelObject = T(responseDictionary: responseDictionary)
  
  return modelObject
}

By adding <T: JSONSerializable> onto the modelFromResponseDictionary method, we’ve specified in the function signature that whatever T is, it must conform to the JSONSerializable protocol. Although we don’t know exactly what modelFromResponseDictionary will return, we do know that it will return a model object that implements JSONSerializable.

Now let’s go ahead and implement JSONSerializable in our Car class:

class Car: NSObject, JSONSerializable {

    var model:String?
    var make:String?

    required init(responseDictionary:Dictionary<String, AnyObject>) {
      if let unwrappedModel:String = responseDictionary["model"] as? String,
        unwrappedMake:String = responseDictionary["make"] as? String {
          self.model = unwrappedModel
          self.make = unwrappedMake
        }
    }
}

And there you have it. Further refactoring could be carried out here, but we’ve arrived at a good stopping point. Using generics, we have structured a simple JSON serializer that is capable of handling a variety of API responses and leaves the model-specific logic to the models themselves.

Swift’s generics give us the ability to write versatile functions that are also type safe. In the past, we might have written out separate serializing logic catered to each individual model object. Now, we achieve the same functionality without duplicating code, and by carefully defining a set of requirements when we write our function signatures, we can dramatically reduce the chances of runtime skulduggery.