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

Declarative Networking with Combine –– iOS Conf SG 2020

Declarative Networking with Combine –– iOS Conf SG 2020

Ritesh Gupta

January 18, 2020
Tweet

More Decks by Ritesh Gupta

Other Decks in Programming

Transcript

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

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

    FlatMap Filter Catch Sink Assign AnySubscriber [Operators] Subscriber
  3. @_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 }
  4. @_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)") } )
  5. @_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)") } )
  6. @_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
  7. @_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
  8. @_riteshhh Validation • move response into correct stream (i.e. 4xx

    response into failure stream and 2xx into output/value stream)
  9. @_riteshhh Validation func validateResponse() -> ValidatedPublisher enum ValidationError: Error {

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

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

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

    .mapError { ValidationError.error($0) } .flatMap { (result) -> ValidatedPublisher in } }
  13. @_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 } } }
  14. @_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 } } }
  15. @_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 } } }
  16. @_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 } } }
  17. @_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() } } }
  18. @_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() } } }
  19. @_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() } } }
  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 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() }
  21. @_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
  22. @_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
  23. @_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
  24. @_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
  25. @_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
  26. @_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