consuming web services with swift and rx
TRANSCRIPT
![Page 1: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/1.jpg)
Consuming Web Serviceswith Swift and Rx
@gonzalezreal
![Page 2: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/2.jpg)
Forget Alamofire
![Page 3: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/3.jpg)
(at least for 1 hour)
![Page 4: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/4.jpg)
Let's build aWeb API Client
from scratch
![Page 5: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/5.jpg)
→ Model requests using enum→ Map JSON without any 3rd party library→ Use RxSwift to compose our API calls
![Page 6: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/6.jpg)
Bordersgithub.com/gonzalezreal/Borders
![Page 7: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/7.jpg)
https://restcountries.eu/rest/v1/name/Germany?fullText=true
![Page 8: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/8.jpg)
[ { "name": "Germany", "borders": [ "AUT", "BEL", "CZE", ... ], "nativeName": "Deutschland", ... }]
![Page 9: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/9.jpg)
https://restcountries.eu/rest/v1/alpha?codes=AUT;BEL;CZE
![Page 10: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/10.jpg)
[ { "name": "Austria", "nativeName": "Österreich", ... }, { "name": "Belgium", "nativeName": "België", ... }, { "name": "Czech Republic", "nativeName": "Česká republika", ... }]
![Page 11: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/11.jpg)
Modeling the API
![Page 12: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/12.jpg)
https://restcountries.eu/rest/v1/name/Germany?fullText=true
→ GET
→ https://restcountries.eu/rest/v1
→ name/Germany
→ fullText=true
![Page 13: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/13.jpg)
enum Method: String { case GET = "GET" ...}
protocol Resource { var method: Method { get } var path: String { get } var parameters: [String: String] { get }}
![Page 14: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/14.jpg)
Resource → NSURLRequest
![Page 15: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/15.jpg)
extension Resource { func requestWithBaseURL(baseURL: NSURL) -> NSURLRequest { let URL = baseURL.URLByAppendingPathComponent(path)
guard let components = NSURLComponents(URL: URL, resolvingAgainstBaseURL: false) else { fatalError("...") }
components.queryItems = parameters.map { NSURLQueryItem(name: String($0), value: String($1)) }
guard let finalURL = components.URL else { fatalError("...") }
let request = NSMutableURLRequest(URL: finalURL) request.HTTPMethod = method.rawValue
return request }}
![Page 16: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/16.jpg)
enum CountriesAPI { case Name(name: String) case AlphaCodes(codes: [String])}
![Page 17: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/17.jpg)
extension CountriesAPI: Resource {
var path: String { switch self { case let .Name(name): return "name/\(name)" case .AlphaCodes: return "alpha" } }
var parameters: [String: String] { switch self { case .Name: return ["fullText": "true"] case let .AlphaCodes(codes): return ["codes": codes.joinWithSeparator(";")] } }}
![Page 18: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/18.jpg)
Demo
![Page 19: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/19.jpg)
Simple JSON decoding
![Page 20: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/20.jpg)
![Page 21: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/21.jpg)
typealias JSONDictionary = [String: AnyObject]
protocol JSONDecodable { init?(dictionary: JSONDictionary)}
![Page 22: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/22.jpg)
func decode<T: JSONDecodable>(dictionaries: [JSONDictionary]) -> [T] { return dictionaries.flatMap { T(dictionary: $0) }}
func decode<T: JSONDecodable>(data: NSData) -> [T]? { guard let JSONObject = try? NSJSONSerialization.JSONObjectWithData(data, options: []), dictionaries = JSONObject as? [JSONDictionary], objects: [T] = decode(dictionaries) else { return nil }
return objects}
![Page 23: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/23.jpg)
struct Country { let name: String let nativeName: String let borders: [String]}
![Page 24: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/24.jpg)
extension Country: JSONDecodable { init?(dictionary: JSONDictionary) { guard let name = dictionary["name"] as? String, nativeName = dictionary["nativeName"] as? String else { return nil }
self.name = name self.nativeName = nativeName self.borders = dictionary["borders"] as? [String] ?? [] }}
![Page 25: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/25.jpg)
Demo
![Page 26: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/26.jpg)
5 min intro toRxSwift
![Page 27: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/27.jpg)
An API for asynchronous programming with observable streams
![Page 28: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/28.jpg)
Observable streams
→ Taps, keyboard events, timers→ GPS events
→ Video frames, audio samples→ Web service responses
![Page 29: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/29.jpg)
Observable<Element>
![Page 30: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/30.jpg)
--1--2--3--4--5--6--|--a--b--a--a--a---d---X
--------JSON-|---tap-tap-------tap--->
![Page 31: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/31.jpg)
Next* (Error | Completed)?
![Page 32: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/32.jpg)
[1, 2, 3, 4, 5, 6].filter { $0 % 2 == 0 }
[1, 2, 3, 4, 5, 6].map { $0 * 2 }
[1, 2, 3, 5, 5, 6].reduce(0, +)
![Page 33: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/33.jpg)
Array<Element>↓
Observable<Element>
![Page 34: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/34.jpg)
The API Client
![Page 35: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/35.jpg)
enum APIClientError: ErrorType { case CouldNotDecodeJSON case BadStatus(status: Int) case Other(NSError)}
![Page 36: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/36.jpg)
NSURLSession
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()let session = NSURLSession(configuration: configuration)
let task = self.session.dataTaskWithRequest(request) { data, response, error in // Handle response}
task.resume()
![Page 37: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/37.jpg)
NSURLSession&
RxSwift
![Page 38: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/38.jpg)
final class APIClient { private let baseURL: NSURL private let session: NSURLSession
init(baseURL: NSURL, configuration: NSURLSessionConfiguration) { self.baseURL = baseURL self.session = NSURLSession(configuration: configuration) } ...}
![Page 39: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/39.jpg)
private func data(resource: Resource) -> Observable<NSData> { let request = resource.requestWithBaseURL(baseURL)
return Observable.create { observer in let task = self.session.dataTaskWithRequest(request) { data, response, error in
if let error = error { observer.onError(APIClientError.Other(error)) } else { guard let HTTPResponse = response as? NSHTTPURLResponse else { fatalError("Couldn't get HTTP response") }
if 200 ..< 300 ~= HTTPResponse.statusCode { observer.onNext(data ?? NSData()) observer.onCompleted() } else { observer.onError(APIClientError.BadStatus(status: HTTPResponse.statusCode)) } } }
task.resume()
return AnonymousDisposable { task.cancel() } } }
![Page 40: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/40.jpg)
Demo
![Page 41: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/41.jpg)
Let's add JSONDecodable to the mix
![Page 42: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/42.jpg)
func objects<T: JSONDecodable>(resource: Resource) -> Observable<[T]> { return data(resource).map { data in guard let objects: [T] = decode(data) else { throw APIClientError.CouldNotDecodeJSON }
return objects }}
![Page 43: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/43.jpg)
extension APIClient { func countryWithName(name: String) -> Observable<Country> { return objects(CountriesAPI.Name(name: name)).map { $0[0] } }
func countriesWithCodes(codes: [String]) -> Observable<[Country]> { return objects(CountriesAPI.AlphaCodes(codes: codes)) }}
![Page 44: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/44.jpg)
Chaining requests
![Page 45: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/45.jpg)
flatMap
![Page 46: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/46.jpg)
![Page 47: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/47.jpg)
The ViewModel
![Page 48: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/48.jpg)
typealias Border = (name: String, nativeName: String)
class BordersViewModel { let borders: Observable<[Border]> ...}
![Page 49: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/49.jpg)
self.borders = client.countryWithName(countryName) // Get the countries corresponding to the alpha codes // specified in the `borders` property .flatMap { country in client.countriesWithCodes(country.borders) } // Catch any error and print it in the console .catchError { error in print("Error: \(error)") return Observable.just([]) } // Transform the resulting countries into [Border] .map { countries in countries.map { (name: $0.name, nativeName: $0.nativeName) } } // Make sure events are delivered in the main thread .observeOn(MainScheduler.instance) // Make sure multiple subscriptions share the side effects .shareReplay(1)
![Page 50: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/50.jpg)
The View(Controller)
![Page 51: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/51.jpg)
private func setupBindings() { ...
viewModel.borders .bindTo(tableView.rx_itemsWithCellFactory) { tableView, index, border in let cell: BorderCell = tableView.dequeueReusableCell() cell.border = border
return cell } .addDisposableTo(disposeBag)}
![Page 52: Consuming Web Services with Swift and Rx](https://reader035.vdocuments.us/reader035/viewer/2022062600/58ad5a2f1a28ab0b0f8b62f3/html5/thumbnails/52.jpg)
Questions?Comments?@gonzalezreal
https://github.com/gonzalezreal/Bordershttp://tinyurl.com/consuming-web-services