Upgrade to Pro — share decks privately, control downloads, hide ads and more …

APILayer: A Swift framework for API layers in i...

APILayer: A Swift framework for API layers in iOS applications

Avatar for you & the gang

you & the gang

October 26, 2015

Other Decks in Technology

Transcript

  1. APILayer Swift framework on top of Alamofire, Swift 1.x and

    2 Evolved during multiple client projects at you & the gang (Jobspotting, HelloFresh, Lufthansa Innovation Hub,…). Make development of API layers in apps: • Simple (use the same patterns in every app) • Fast (don’t rely on the actual API) • Fun (we love the Swift)
  2. APILayer API TokenRefreshDelegate ParameterMapper MockProtocol RouterProtocol • The main class

    is called API, class methods only • Configurable using three protocols and one class
  3. APILayer API TokenRefreshDelegate ParameterMapper MockProtocol RouterProtocol • The main class

    is called API, class methods only • Configurable using three protocols and one class MockProtocol 1/5
  4. We need an API? • Many clients begin API and

    app at the same time • Either fresh, or moving from a big website to API MockProtocol 2/5
  5. What is happening? • API implies the model used by

    the app • API missing / changing = model missing / changing MockProtocol 3/5
  6. Give me the JSON! • Get JSON files for each

    API endpoint • Does not have to be the final state. Get started! UI, UX, all the things… Finalise Mock Responses Actual API 1. 2. MockProtocol 4/5
  7. MockProtocol public protocol MockProtocol { func path(forRouter router: RouterProtocol) ->

    String? } API MockProtocol JSON File in App Bundle API.request(Router.GetCollection) {… Response Object Deserialization • MockProtocol works based on regular routers • API.mocker is optional (remove to go „live“) MockProtocol 5/5
  8. APILayer API TokenRefreshDelegate ParameterMapper MockProtocol RouterProtocol • The main class

    is called API, class methods only • Configurable using three protocols and one class RouterProtocol 1/2
  9. Request Construction public protocol RouterProtocol { var method: Alamofire.Method {

    get } var path: String { get } var encoding: ParameterEncoding { get } var baseURLString: String { get } var uploadData: (data: NSData, name: String, fileName: String, mimeType: String)? { get } } enum Router: RouterProtocol { case GetAuthToken(username: String, password: String) case GetOffice(officeName: String) case GetMember(officeName: String, memberPIN: String) … • Requests are build using RouterProtocol • We use an enum for all the routers • Parameters in the case • Switch on the router RouterProtocol 2/2
  10. APILayer API TokenRefreshDelegate ParameterMapper MockProtocol RouterProtocol • The main class

    is called API, class methods only • Configurable using three protocols and one class ParameterMapper 1/4
  11. ParameterMapper Used by the API to get parameters and headers

    from routers, set by API.parameterMapper: public class ParameterMapper { public func parametersForRouter(router: RouterProtocol) -> [String : AnyObject] public func headersForRouter(router: RouterProtocol) -> [String : String] … API.parameterMapper = Mapper() ParameterMapper 2/4
  12. ParameterMapper • Used during response object deserialisation for value extraction

    from the response JSON • Type checks and optionals • Types are inferred by the property class MemberStatus: ResponseObjectSerializable { let keys = (checkedIn: "checked_in", etaMinutes: "eta_minutes", isNear: "is_near") let checkedIn: Bool let etaMinutes: Int? let isNear: Bool? required init(representation: AnyObject, inout error: ErrorType?) { let mapper = API.parameterMapper checkedIn = mapper.value(fromRepresentation: representation, key: keys.checkedIn, error: &error) etaMinutes = mapper.value(fromRepresentation: representation, key: keys.etaMinutes) isNear = mapper.value(fromRepresentation: representation, key: keys.isNear) } } ParameterMapper 3/4
  13. ParameterMapper • Supports entity types, entity arrays, dates, etc… •

    Date format is specified as a property let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.S" API.parameterMapper.dateFormatter = dateFormatter … class Office: ResponseObjectSerializable { let keys = (displayName: "display_name", officeName: "name", members: "members", founded: "founded") let displayName: String let founded: NSDate let officeName: String let members: [Member] required init(representation: AnyObject, inout error: ErrorType?) { let mapper = API.parameterMapper displayName = mapper.value(fromRepresentation: representation, key: keys.displayName, error: &error) officeName = mapper.value(fromRepresentation: representation, key: keys.officeName, error: &error) founded = mapper.value(fromRepresentation: representation, key: keys.founded, error: &error) members = mapper.value(fromRepresentation: representation, key: keys.members, error: &error) } } ParameterMapper 4/4
  14. APILayer API TokenRefreshDelegate ParameterMapper MockProtocol RouterProtocol • The main class

    is called API, class methods only • Configurable using three protocols and one class TokenRefreshDelegate 1/4
  15. TokenRefreshDelegate • Authentication Tokens do need refreshing sometimes • APILayer

    has a pattern for this API TokenRefreshDelegate tokenRefreshIsIndicated(response) true tokenRefresh(completion) completion(success) TokenRefreshDelegate 2/4
  16. TokenRefreshDelegate • All requests are triggered using a enqueued operation

    • Operations are non-blocking, so still in parallel API.request(Router.GetCollection) {… performRouter() performRouter() performRouter() Serial Operation Queue enqueues non-blocking operation TokenRefreshDelegate 3/4
  17. TokenRefreshDelegate • If token refresh is needed, a blocking refresh

    operation is enqueued • The failed request and all other failing requests are enqueued again to be performed after the refresh • Seamless for the calling code (just some little delay) performRouter() TokenRefreshDelegate.tokenRefresh() Serial Operation Queue API.tokenRefresh() TokenRefreshDelegate 4/4
  18. Object Deserialization public protocol ResponseObjectSerializable { init(representation: AnyObject, inout error:

    ErrorType?) } • Still looking for a perfect solution • Current pattern is nice to read and use from the outside
  19. Object Deserialization class DemoItem: ResponseObjectSerializable { let keys = (itemId:

    "id", title: "title", awesomeCount: "awesome_count", subItem: "subitem") let itemId: String let title: String let awesomeCount: Int? let subItem: SubItem? required init(representation: AnyObject, inout error: ErrorType?) { let mapper = API.parameterMapper itemId = mapper.value(fromRepresentation: representation, key: keys.itemId, error: &error) title = mapper.value(fromRepresentation: representation, key: keys.title, error: &error) awesomeCount = mapper.value(fromRepresentation: representation, key: keys.awesomeCount) subItem = mapper.value(fromRepresentation: representation, key: keys.subItem) } } • Let properties, optionals, keys • No Serialization for free however • toDict() when we need it