Comments (1)
Proposed Solutions
- Change
ServiceTaskResult
from an Enumeration to a Class. - Overload ServiceTask's response handler API with Obj-C friendly methods
- 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 becauseNSError
is required to expose theServiceTaskResult
class to Obj-C. - Lose the brevity and expressiveness of using enum cases.
return .Value(models)
now becomesreturn 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)
- update example project to work with 2.0 API changes
- update readme and programming guide for 2.0 API
- ServiceTaskResult.Value does not support structs
- Carthage build failed HOT 1
- Remove `ServiceTaskResult` to simplify handler API
- Obj-C Interop: `updateUI` block not called when value is `nil` HOT 3
- enable response handlers to infer the handler result type HOT 1
- Certain handlers only called after response is processed
- `updateUI()` and `updateErrorUI()` don't block other handlers; should they? HOT 4
- Add API for setting URL and body parameters separately HOT 2
- Separate "Request" and "Task" APIs? HOT 2
- support for Image upload with data HOT 1
- bump deployment target to 8.2
- Support for Swift 3 HOT 2
- Add metrics API that utilizes NSURLSessionTaskMetrics
- Add `ServiceRequest` API
- query parameter value encoding is failing to URL-encode `+` HOT 1
- add `updateErrorUI()` response handler that runs on the main queue
- make request options work differently, see below.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from elwebservice.