> On Jun 6, 2016, at 3:48 PM, [email protected] wrote:
>
> Yes, ok...
>
> Well, I cannot easily make everything synchronous. I just can change it in a
> way that makes a `@required` attribute "optional" ;)
>
> import Foundation
>
> enum Result<T> {
> case success(T)
> case error(ErrorType)
> }
>
> enum MyError: ErrorType {
> case unknownError
> case corruptData
> case badStatusCode(Int)
> }
>
> struct SomeThing {
> init?(data: NSData) {
> ...
> }
> }
>
> func getSomethingFromTheNetwork(url: NSURL, completionHandler:
> (Result<SomeThing>) -> ()) {
> func toSomeThing(data: NSData?, response: NSURLResponse?, error: NSError?)
> -> Result<SomeThing> {
> if let error = error {
> return .error(error)
> }
>
> if let httpResponse = response as? NSHTTPURLResponse {
> let statusCode = httpResponse.statusCode
>
> if statusCode < 200 || statusCode >= 300 {
> return .error(MyError.badStatusCode(statusCode))
> }
> }
>
> guard let data = data else {
> return .error(MyError.unknownError)
> }
>
> guard let something = SomeThing(data: data) else {
> return .error(MyError.corruptData)
> }
>
> return .success(something)
> }
> let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data,
> response, error in
> completionHandler(toSomething(data, response, error))
> }
>
> task.resume()
> }
I’ve written code like that; however, it falls down as soon as you have to nest
two or more asynchronous APIs. Consider the following, and assume we don’t have
control of SomeThing’s API (or, maybe, SomeThing relies on some other API we
don’t control that has to be asynchronous):
import Foundation
enum Result<T> {
case success(T)
case error(ErrorType)
}
enum MyError: ErrorType {
case unknownError
case corruptData
case badStatusCode(Int)
}
struct SomeThing {
make(data: NSData, completionHandler: (SomeThing?) -> ())
}
func getSomethingFromTheNetwork(url: NSURL, completionHandler:
(Result<SomeThing>) -> ()) {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data,
response, error in
if let error = error {
completionHandler(.error(error))
return
}
if let httpResponse = response as? NSHTTPURLResponse {
let statusCode = httpResponse.statusCode
if statusCode < 200 || statusCode >= 300 {
completionHandler(.error(MyError.badStatusCode(statusCode)))
return
}
}
guard let data = data else {
completionHandler(.error(MyError.unknownError))
return
}
SomeThing.make(data) { something in
if let something = something {
completionHandler(.success(something))
} else {
completionHandler(.error(MyError.corruptData))
}
}
}
task.resume()
}
Yeah, you can wrap it in yet another function, but if things get complex
enough, and you forget to do that somewhere, it’s bad news.
> With a semaphore, I can also make it synchronous. Not sure if this is a good
> idea though... If the API is already asynchronous, it's probably better to
> use it that way.
It’s not a good idea. As I mentioned before, Apple recommends against blocking
on dispatch queues. It’s not considered good practice. That includes the main
queue, by the way; both Cocoa-Dev and Stack Overflow are filled with questions
from people who tried using dispatch_sync or dispatch_async to the main queue
to run -[NSOpenPanel runModal], and then got confused when nothing in the panel
displayed properly. Using the async methods on NSOpenPanel and returning via a
completion handler would have caused this to work properly.
Charles
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution