Declarative Networking with Combine –– iOS Conf SG 2020

Declarative Networking with Combine –– iOS Conf SG 2020

8d05c774222cd7ee18ea73005ff37a55?s=128

Ritesh Gupta

January 18, 2020
Tweet

Transcript

  1. #DroidConBos @ragdroid Declarative Networking with Combine Ritesh Gupta iOS Conf

    SG 2020
  2. @_riteshhh fueled.com/ritesh twitter.com/@_riteshhh riteshhh.com github.com/riteshhgupta Ritesh Gupta

  3. @_riteshhh Combine It’s a new reactive framework by Apple which

    provides a declarative Swift API for processing values over time.
  4. @_riteshhh Pillars of Combine Publisher Just Fail Future DataTaskPublisher Map

    FlatMap Filter Catch Sink Assign AnySubscriber [Operators] Subscriber
  5. @_riteshhh Networking • URLSession • URLSessionDataTask • func dataTask(URL) ->

    URLSessionDataTask (without Combine)
  6. @_riteshhh Networking (with Combine) • URLSession • URLSession.DataTaskPublisher • func

    dataTaskPublisher(URL) -> DataTaskPublisher
  7. @_riteshhh DataTaskPublisher typealias DataTaskResult = (Data, URLResponse) public struct DataTaskPublisher

    : Publisher { /// The kind of values published by this publisher. public typealias Output = DataTaskResult /// The kind of errors this publisher might publish. public typealias Failure = URLError }
  8. @_riteshhh DataTaskPublisher let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session =

    URLSession.shared session .dataTaskPublisher(for: url) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } )
  9. @_riteshhh DataTaskPublisher let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session =

    URLSession.shared session .dataTaskPublisher(for: url) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } )
  10. @_riteshhh DataTaskPublisher let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session =

    URLSession.shared session .dataTaskPublisher(for: url) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) •Gateway timeout error •No internet error
  11. @_riteshhh DataTaskPublisher let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session =

    URLSession.shared session .dataTaskPublisher(for: url) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) •401: Invalid API Key •404: Not found
  12. @_riteshhh Validation • move response into correct stream (i.e. 4xx

    response into failure stream and 2xx into output/value stream)
  13. @_riteshhh Validation func validateResponse()

  14. @_riteshhh Validation func validateResponse() -> ValidatedPublisher enum ValidationError: Error {

    case error(Error) case jsonError(Data) } typealias ValidatedPublisher = AnyPublisher<DataTaskResult, ValidationError> typealias DataTaskResult = (Data, URLResponse)
  15. @_riteshhh Validation func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher typealias IsValidCallback =

    (DataTaskResult) -> Bool typealias ValidatedPublisher = AnyPublisher<DataTaskResult, ValidationError> typealias DataTaskResult = (Data, URLResponse)
  16. @_riteshhh Validation extension Publisher where Output == DataTaskResult { func

    validateResponse(isValid: IsValidCallBack) -> ValidatedPublisher }
  17. @_riteshhh Validation func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self

    }
  18. @_riteshhh Validation func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self

    .mapError { ValidationError.error($0) } }
  19. @_riteshhh Validation func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self

    .mapError { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in } }
  20. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output/Success stream } else { // return the value in the Failure stream } } }
  21. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output/Success stream } else { // return the value in the Failure stream } } }
  22. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream } } }
  23. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream } } }
  24. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream return Fail( outputType: DataTaskResult.self, failure: ValidationError.jsonError(result.data) ).eraseToAnyPublisher() } } }
  25. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream return Fail( outputType: DataTaskResult.self, failure: ValidationError.jsonError(result.data) ).eraseToAnyPublisher() } } }
  26. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream return Fail( outputType: DataTaskResult.self, failure: ValidationError.jsonError(result.data) ).eraseToAnyPublisher() } } }
  27. @_riteshhh func validateResponse(isValid: IsValidCallback) -> ValidatedPublisher { return self .mapError

    { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in if isValid(result) { // return the value in the Output stream return Just(result) .setFailureType(to: ValidationError.self) .eraseToAnyPublisher() } else { // return the value in the Failure stream return Fail( outputType: Output.self, failure: ValidationError.jsonError(result.data) ).eraseToAnyPublisher() } }.eraseToAnyPublisher() }
  28. @_riteshhh func validateStatusCode(_ isValid: (Int) -> Bool) -> ValidatedPublisher {

    return validateResponse { (data, response) in let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 return isValid(statusCode) } } func validateResponse(isValid: (DataTaskResult) -> Bool) -> ValidatedPublisher { ... } Validation
  29. @_riteshhh let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session = URLSession.shared

    session .dataTaskPublisher(for: url) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) Declarative Networking
  30. @_riteshhh let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session = URLSession.shared

    session .dataTaskPublisher(for: url) .validateStatusCode { (200..<300).contains($0) } .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) Declarative Networking
  31. @_riteshhh let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session = URLSession.shared

    session .dataTaskPublisher(for: url) .validateStatusCode { (200..<300).contains($0) } .validateResponse { !$0.data.isEmpty } .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) Declarative Networking
  32. @_riteshhh let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session = URLSession.shared

    session .dataTaskPublisher(for: url) .validateStatusCode { (200..<300).contains($0) } .validateResponse { !$0.data.isEmpty } .mapErrorJson(to: ApiErrorResponse.self, decoder: decoder) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) Declarative Networking
  33. @_riteshhh let url = URL(string: "http://api.openweathermap.org/data/2.5")! let session = URLSession.shared

    session .dataTaskPublisher(for: url) .validateStatusCode { (200..<300).contains($0) } .validateResponse { !$0.data.isEmpty } .mapErrorJson(to: ApiErrorResponse.self, decoder: decoder) .mapValueJson(to: ApiValueResponse.self, decoder: decoder) .sink( receiveCompletion: { print("Completion/Failure: \($0)") }, receiveValue: { print("Value: \($0)") } ) Declarative Networking
  34. @_riteshhh Further reading • Declarative Networking with Combine –– riteshhh.com

    • Awesome Combine –– Github
  35. @_riteshhh Thank you!