Code Monkey home page Code Monkey logo

Comments (1)

angelodipaolo avatar angelodipaolo commented on June 27, 2024

Proposed Solutions

  1. Change ServiceTaskResult from an Enumeration to a Class.
  2. Overload ServiceTask's response handler API with Obj-C friendly methods
  3. Add specially named methods to ServiceTask's response handler API (not overloads)

1. Change ServiceTaskResult from an Enumeration to a Class.

ServiceTaskResult becomes a class that provides two initializers, one for errors and another for the actual value payload.

@objc public final class ServiceTaskResult: NSObject {
    private(set) var value: AnyObject?
    private(set) var error: NSError?

    @objc public init(value: AnyObject) {
        self.value = value
    }

    @objc public init(error: NSError) {
        self.error = error
    }
}

Old concise syntax:

.responseJSON { json in
      if let models: [Brewer] = JSONDecoder<Brewer>.decode(json)  {
          // pass encoded value via ServiceTaskResult
          return .Value(models)
      } else {
        // any value conforming to ErrorType
        return .Failure(JSONDecoderError.FailedToDecodeBrewer) 
      }
    }

New crufty syntax:

.responseJSON { json in
      if let models: [Brewer] = JSONDecoder<Brewer>.decode(json)  {
          // pass encoded value via ServiceTaskResult
          return ServiceTaskResult(value: models)
      } else {
        // need to return NSError instead of ErrorType
        return NSError(domain: "foo", code: 500, userInfo: nil)
      }
    }

pros

  • Makes ServiceTask's API callable from Obj-C

cons

  • Conflates "Result" API with separate types for both Objective-C and Swift
  • Lose the use of ErrorType when calling from Swift because NSError is required to expose the ServiceTaskResult class to Obj-C.
  • Lose the brevity and expressiveness of using enum cases. return .Value(models) now becomes return ServiceTaskResult(value: models)

2. Overload ServiceTask's response handler API with Obj-C friendly methods

Keep ServiceTaskResult as an enum but add a ObjCHandlerResult class that encapsulates the result/error data and can be initialized from Obj-C.

@objc public final class ObjCHandlerResult: NSObject {
    private(set) var value: AnyObject?
    private(set) var error: NSError?

    @objc public init(value: AnyObject) {
        self.value = value
    }

    @objc public init(error: NSError) {
        self.error = error
    }
}

Overload the response handler methods to accept a closure which returns a ObjCHandlerResult value.

extension ServiceTask {
    // existing method that cannot be represented in Obj-C
    public func response(handler: ResponseProcessingHandler) -> Self {
        dispatch_async(handlerQueue) {
            if let taskResult = self.taskResult {
                switch taskResult {
                case .Failure(_): return // bail out to avoid next handler from running
                case .Value(_): break
                case .Empty: break
                }
            }

            self.taskResult = handler(self.responseData, self.urlResponse)
        }

        return self
    }

    // overload with Obj-C friendly closure return type
    @objc public func response(handler: (NSData?, NSURLResponse?) -> ObjCHandlerResult?) -> Self {
        let processResponse: ResponseProcessingHandler = { data, response in
            let result = handler(data, response)

            if let error = result?.error {
                return .Failure(error)
            } else if let value = result?.value {
                return .Value(value)
            } else {
                return .Empty
            }
        }

        return response(processResponse)
    }
}

Example Objective-C usage:

ServiceTask *task = [service GET:@"/brewers"];

[task responseJSON:^HandlerResult *(id json) {
    NSArray *models = [self decodeBrewersFromJSON:json];

    if (models == nil) {
        NSError *error
        return [[HandlerResult alloc] initWithError:error];
    } else {
        return [[HandlerResult alloc] initWithValue:models];
    }

}];

[task updateUI:^(id value) {
    if ([value isKindOfClass:[NSArray class]]) {
         // update some UI with brewer models
    }
}];

[task resume];

Example Swift usage:

service
    .GET("/brewers")
    .responseJSON { json in
      if let models: [Brewer] = JSONDecoder<Brewer>.decode(json)  {
          // pass encoded value via ServiceTaskResult
          return .Value(models)
      } else {
        // any value conforming to ErrorType
        return .Failure(JSONDecoderError.FailedToDecodeBrewer) 
      }
    }
    .updateUI { value in
        if let brewers = value as? [Brewer] {
            // update some UI with brewer models
        }
    }
    .resume()

pros

  • Makes ServiceTask's API callable from Obj-C
  • Retains the brevity and expressiveness of using ServiceTaskResult enum cases in Swift

cons

  • Conflates "Result" API with separate types for both Objective-C and Swift
  • Causes ambiguity when calling response handler methods which can only be resolved by explicitly declaring the type of the handler closure. This breaks the chaining syntax you get with trailing closures.

Currently you can use trailing closure syntax to chain ServiceTask methods.

extension ServiceTask {
    func responseAsBrews(handler: ([Brew]) -> Void) -> Self {
        return
            // chaining with trailing closures 
            responseJSON { json in
                if let json = self.jsonObject(json, forKey: "brews"),
                    let jsonArray = json as? [AnyObject],
                    let decodedArray = ModelDecoder<Brew>.decodeArray(jsonArray) {
                        return .Value(decodedArray)
                } else {
                    return .Failure(ServiceTaskDecodeError.FailedToDecodeJSONArray)
                }
            }
            .updateUI { value in
                if let brews = value as? [Brew] {
                    handler(brews)
                }
            }
    }
}

The overloaded methods cause ambiguity and break trailing closure chaining which means the closures must be explicitly typed. This is not necessarily a con and can encourage more reusable code since the closure cannot be defined inline when calling the response handler methods. The example below uses a computed property to define the handler closure which can be reused in other response handlers.

extension ServiceTask {
    // explicitly typed closure
    // declaring a property
    var parseBrewModelsHandler: JSONHandler {
        return { json in
            if let json = self.jsonObject(json, forKey: "brews"),
                let jsonArray = json as? [AnyObject],
                let decodedArray = ModelDecoder<Brew>.decodeArray(jsonArray) {
                    return .Value(decodedArray)
            } else {
                return .Failure(ServiceTaskDecodeError.FailedToDecodeJSONArray)
            }
        }
    }

    func responseAsBrews(handler: ([Brew]) -> Void) -> Self {
        return
            responseJSON(parseBrewModelsHandler)
            .updateUI { value in
                if let brews = value as? [Brew] {
                    handler(brews)
                }
            }
    }
}

3. Add specially named methods to ServiceTask's response handler API (not overloads)

Add specially named methods that are designed only to be called from Obj-C. Same approach as #2 except it avoids the ambiguity that breaks the closure type inference.

extension ServiceTask {
    // existing method that cannot be represented in Obj-C
    public func response(handler: ResponseProcessingHandler) -> <<error type>>

    // specially-named obj-c friendly method
    @objc public func responseObjC(handler: (NSData?, NSURLResponse?) -> ObjCHandlerResult?) -> Self
}

pros

  • Makes ServiceTask's API callable from Obj-C
  • Avoids the ambiguity introduced by #2 which breaks trailing closure chaining

cons

  • Conflates the ServiceTask response handler API by having separate methods for adding response handlers with different names.
  • Conflates "Result" API with separate types for both Objective-C and Swift

from elwebservice.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.