[swift-evolution] Pitch: @required attribute for closures
michael.peternell at gmx.at
michael.peternell at gmx.at
Mon Jun 6 15:48:17 CDT 2016
> Am 06.06.2016 um 22:30 schrieb Charles Srstka via swift-evolution <swift-evolution at swift.org>:
>
> On Jun 6, 2016, at 2:49 PM, Michael Peternell <michael.peternell at gmx.at> wrote:
>>
>> That's really hard to answer in the general case. I think real proposals should contain concrete, realistic examples that show the benefit of the proposal. It's really hard to argue against a proposal if there is no such example. User feedback from a sheet is one of the few examples where asynchronous programming makes sense: But I cannot see how a `@required` annotation would be useful in that setting.
>
> Quick-n-dirty example, written in Mail. How would you make this synchronous?
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()
}
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.
/// Should not be called from the main thread
func getSomethingFromTheNetworkSync(url: NSURL) -> 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 sema = dispatch_semaphore_create(0)
var result = Result<Something>?
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
result = toSomething(data, response, error)
dispatch_semaphore_signal(sema)
}
task.resume()
dispatch_semaphore_wait(sema)
return result!
}
The other example can be transformed in the same way..
-Michael
>
> 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>) -> ()) {
> 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
> }
>
> guard let something = SomeThing(data: data) else {
> completionHandler(.error(MyError.corruptData))
> return
> }
>
> completionHandler(.success(something))
> }
>
> task.resume()
> }
>
> (disclaimer: yes, we’d probably have to improve the API for NSURLSession a bit here for @required to be useful.)
>
> How would you make this synchronous:
>
> func getSomethingFromAnotherTask(completionHandler: (Result<SomeThing>) -> ()) {
> let message = ...
>
> xpc_send_message_with_reply(self.connection, message, self.dispatchQueue) { reply in
> do {
> let something = try self.turnReplyIntoSomethingSomehow(reply)
>
> completionHandler(.success(something))
> } catch {
> completionHandler(.error(error))
> }
> }
> }
>
> Or this:
>
> func doSomethingThatNeedsUserInput(completionHandler: (Bool) -> ()) {
> let alert = NSAlert()
>
> alert.messageText = “Should we continue?”
> alert.addButtonWithTitle(“Continue”)
> alert.addButtonWithTitle(“Cancel”)
>
> alert.beginSheetModalForWindow(someWindow) { response in
> if response == NSAlertFirstButtonReturn {
> completionHandler(true)
> }
>
> // uh oh, I forgot to test for other conditions, and now the completion handler won’t be called if the user clicked “Cancel”.
> // Too bad the compiler couldn’t warn me about it.
> }
> }
>
> There are some tasks which synchronous programming is simply not well-suited for.
>
> Charles
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list