Active Filters: Swift

Continuing where we left off in Bite #93 with our Alamofire Router, today we'll take a look at creating a Custom Response Serializer.

These are the mechanisms through which the Alamofire HTTP library translates the HTTP responses it receives into types that are more friendly to work with.

Alamofire ships with a few built-in serializers for common things like raw data, text, JSON and property lists. Today we'll be taking a look at adding our own. Let's get started.

We want to make it super easy to translate Alamofire responses into structs that conform to the Decodable protocol from the Decodable JSON parsing library. (Bite #86).

extension Alamofire.Request {
  public func responseCollection<T: Decodable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
    let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in

      guard error == nil else { return .Failure(error!) }

      let result = Alamofire
        .Request
        .JSONResponseSerializer(options: .AllowFragments)
        .serializeResponse(request, response, data, error)

      switch result {
      case .Success(let value):
        do {
          return .Success(try [T].decode(value))
        } catch {
          return .Failure(Error.errorWithCode(.JSONSerializationFailed,
            failureReason: "JSON parsing error, JSON: \(value)"))
        }
      case .Failure(let error): return.Failure(error)
      }
    }

    return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
  }
}

First we'll extend Request and add a new generic responseCollection function that returns a Response struct.

Notice that in the generic part of the function definition we allow any type T that confirms to the Decodable protocol.

Now the fun part. We'll first serialize the response using the built-in JSON response serializer, then switch on its result, returning the failure case if it exists.

If the JSON was parsed successfully, we'll try to decode using Decodable.

Now we can try both our Router and our new custom response serializer out together:

Alamofire.request(LBOC.Router.Bites)
  .responseCollection { (response: Response<[LBOC.Bite], NSError>) in
    guard response.result.error == nil else { return }

    let bites = response.result.value

    print(bites)
}

The responseCollection function handles arrays of objects. Making a responseObject serializer function to handle single object responses is almost identical. Simply replace all occurrences of [T] with T.

Alamofire 3.0 will be released soon so now is a good time to start looking at how best to build an API client for our app. We'll be covering different areas of this topic periodically over the next few weeks.

Today we'll start by looking at one part of the equation: routing.

We've talked about routing before in the context of iOS URL schemes and libraries like JLRoutes (Bite #62). This is a bit different. We'll be creating a Router type that will help us unify and simplify our code when we make requests by generating URL requests from enum cases.

enum Router: URLRequestConvertible {
  static let baseURL = NSURL(string: "https://littlebitesofcocoa.com")!

  case Bites
  case Bite(Int)
  case BitesTagged(Int)

  var URL: NSURL { return Router.baseURL.URLByAppendingPathComponent(route.path) }

  var route: (path: String, parameters: [String : AnyObject]?) {
    switch self {
      case .Bites: return ("/", nil)
      case .Bite (let biteID): return ("/\(biteID)", nil)
      case .BitesTagged(let tagID): return ("/", ["t": tagID])
    }
  }

  var URLRequest: NSMutableURLRequest {
    return Alamofire
      .ParameterEncoding
      .URL
      .encode(NSURLRequest(URL: URL), parameters: (route.parameters ?? [ : ])).0
  }
}

We'll start by declaring a new enum type that conforms to the URLRequestConvertable protocol (which comes from Alamofire). Then we'll define a few different cases that each correspond to an endpoint on our HTTP API. We'll add a computed route property that returns a tuple to translate from our enum cases into URL path and parameter values.

Finally, we'll finish conforming to URLRequestCovertable by adding a computed property that encodes our URL and parameters into an NSMutableURLRequest. Neat.

Now we can use our new Router to write some super clean Alamofire request code like this:

Alamofire.request(Router.Bites)
  .responseObjects { (response: Response<[Bite], NSError>) in
    let bites = response.result.value
    print(bites)
  }

The responseObjects custom response serializer converts JSON to our own custom struct types. We'll look at how to create it in the near future.

We can also access raw URL requests like this:

let request = Router.Bite(93).URLRequest

Topics

#34: Swift Generics 🐦

Topics

Generics were new to many iOS developers when they debuted in Swift 1.0 last year. They are a way of specifying parts of your code can work with different kinds of objects of a given type. but you don't want to hardcode one single type. What if you wanted to say something like β€œthis function accepts any object of some type and returns an object of that same type, but I don't care what type that is.” It ends up looking like this:

func aGenericFunction<SomeType>(thing: SomeType) -> SomeType {
  return thing
}

aGenericFunction("Hello") // returns the String "Hello"

Whoa! Where did those < > brackets come from? That's how you tell the Swift compiler that you are writing a generic function. The SomeType can be any name you want, and you can use it in your code in place of a concrete type. What if you wanted your new generic function to only accept types that conformed to the a protocol? Simple:

func aGenericFunction<T : Requestable>(thing: T) -> T {
  return thing
}

Functions are great, but what about types themselves? The concepts behind Generics work exactly the same there. Let's look at this by making a generic DataSource struct that will power a table view controller:

struct DataSource<ObjectType> {
  var objects = [ObjectType]()

  func objectAtIndex(index: Int) -> ObjectType {
    return objects[index]
  }
}

Now we can add a property to our view controllers like this:

class SpaceshipsViewController : UITableViewController {
  var dataSource = DataSource<Spaceship>()
}

Patterns are one of the most powerful parts of Swift. Let's look at a few examples of cool things you can do with pattern matching.

Ranges

let since: NSTimeInterval = 127 // 2 minutes, 7 seconds

switch since {
    case (0...10): print("just now")
    case (10...60): print("a minute ago")
    default: print("\(since) seconds")
}

Enums

enum Result {
  case Success
  case Error(String)
}

let r = Result.Success

switch r {
  case .Success: print("Yay!")
  case .Error(let err): print("Boo: \(err)")
}

Types

let something = Product()

switch something {
    case is Product: print("Found a product")
    case let person as Person: print("Found a person: \(person.name)")
    default: print("Found an unknown thing.")
}

Where

let contentOffset = (0, 30.0)

switch contentOffset {
  case let (_, y) where y < 0: print("Scrolled up")
  case let (_, y) where (0...60) ~= y: print("scrolling top area")
  default: println("just scrolling")
}

Swift 2

New in Swift 2, you can now use any Swift pattern as the clause of an if statement:

let comments = 7

if case 1...10 = commentCount {
    print("some comments")
} else if case 11..100 = commentCount {
    print("lots of comments")
}

And in Swift 2's new error handling:

do {
    try send(message: "Hello")
} catch SendError.Offline {
    print("user is offline")
} catch SendError.RateLimited {
    print("stop spamming")
} catch SendError.Blocked(let reason) {
    print("user was blocked because \(reason)")
}

Topics

#19: Protocol Extensions πŸ”­

Topics

Protocol Extensions are a new feature of Swift 2. They allow you to add new functions (complete with implementations) to any class that implements a protocol. The functionality is best explained with a simple example. Let’s say you wanted to add a way to shuffle arrays.

In Swift 1.2 and earlier, you’d probably add a shuffle function using something like this:

extension Array {
  mutating func shuffle() {
    for i in 0..<(count - 1) {
      let j = Int(arc4random_uniform(UInt32(count - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

Which works great, but only applies to Array types. In Swift 2, you can add it to all mutable collections using Protocol Extensions.

extension MutableCollectionType where Self.Index == Int {
  mutating func shuffleInPlace() {
    let c = self.count
    for i in 0..<(c - 1) {
      let j = Int(arc4random_uniform(UInt32(c - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

As you can see here, we're also able to use Swift’s fantastic pattern-matching support to limit our extension to only mutable collections that use an Int as their index.

Topics

#4: Singletons 🚹

Topics

Singletons are a design pattern describing globally accessible instances of objects.

They should only be used when we need to read and/or write some state globally and possibly from other threads.

class VehicleManager {
  static let sharedManager = VehicleManager()

  var cars: [Car] = []

  init() {
    // init some (likely) global state
  }
}

With a singleton, there's a function that can be called from anywhere. In this case it's VehicleManager.sharedManager.

A great side effect is that the initializer for VehicleManager() isn't called until we access .sharedManager the first time.

Singletons TLDR;

  • πŸ‘ Globally accessible
  • πŸ‘ Thread-safe
  • πŸ‘ Lazily initialized
  • 😭 Technique shown here requires Swift 1.2 or later

Topics

#3: Timepiece πŸ•™

Topics

Timepiece is a library from Naoto Kaneko that makes working with NSDate objects a bit more intuitive.

let now = NSDate()
let theForceAwakens = now + 218.days + 2.hours + 34.minutes

It also aids in quickly parsing dates from or rendering dates to a String:

let judgementDay = "1997-08-29".dateFromFormat("yyyy-MM-dd")
1.day.later.stringFromFormat("EEEE") // "Thursday"

Comparisons are simplified as well:

if (birthday > 40.years.ago) {
  midLifeCrisis()
}

How to Use Timepiece

In our Podfile:

platform :ios, '8.0'

pod 'Timepiece'

In any Swift file:

import Timepiece

More info about Timepiece can be found at git.io/timepiece

Topics

#2: Chainable Methods πŸ”—

Topics

Making functions chainable is quite easy and can allow us to write using an almost DSL-like syntax.

We'll add a new function that does something and then return self. It's that simple.

enum CountdownType: Int { case ToTheSecond, ToTheDay }
enum ColorScheme: Int { case AfterMidnight, ClassyYellow, Tealfish }

class Concern {
  var title: String = ""
  func title(aTitle: String?) -> Concern {
    title = aTitle ?? ""; return self
  }

  var subtitle = ""
  func subtitle(aSubtitle: String?) -> Concern {
    subtitle = aSubtitle ?? ""; return self
  }

  var countdownType: CountdownType = .ToTheSecond
  func countdownType(type: CountdownType) -> Concern {
    countdownType = type; return self
  }

  var colorScheme: ColorScheme = .AfterMidnight
  func colorScheme(scheme: ColorScheme) -> Concern {
    colorScheme = scheme; return self
  }
}

It enables us to write extremely readable and composable code like this:

Concern()
  .title("Big Meeting")
  .subtitle("With those people from that place")
  .countdownType(.ToTheDay)
  .colorScheme(.Tealfish)

Setting properties is just the tip of the iceberg here. Imagine using this technique to create chainable queries:

Event
  .withCategory(.Meeting)
  .withAttendees([User.me])
  .sort { $0.startDate < $1.startDate }

Chainables TLDR;

  • πŸ‘†write setters
  • πŸ–– return self
  • πŸŽ‰ profit

Topics

#1: View Controller Initialization πŸš€

Topics

So, we're crafting our brand new view controller and BAM! we see this:



Let's try to break down how we got here:

Subclasses only inherit their superclass's initializers if certain conditions are met.

If our subclass doesn't define any designated initializers, it automatically inherits all of its superclass's designated initializers.

If not though, then it inherits none of them.

We subclassed UIViewController.

UIViewController implements NSCoding.

NSCoding requires init(coder:) be implemented.

By default UIViewController implements init(coder:) for us, and we don't need to do a thing.

But now, we defined our own new designated initalizer called init(), so we've stopped meeting the conditions for inheriting all of our superclass's designated initializers, and now have to override, implement and properly call super on init(coder:) all by ourselves!

Page 4 of 4