Swift中Completion Handler为何需添加下划线字符才能编译?
这个问题其实涉及到Swift中闭包和普通函数的参数语法差异,我来给你掰扯清楚:
核心原因:闭包参数默认没有外部标签
Swift里,普通函数的参数可以同时有外部参数名(调用时用来标注,提升可读性)和内部参数名(函数内部使用),比如:
func calculateSum(a: Int, b: Int) -> Int { return a + b } // 调用时可以写 calculateSum(a: 1, b: 2)
但闭包的参数列表不一样——它默认没有外部参数名,你定义闭包类型时写的参数名称,仅仅是闭包内部可以使用的内部参数名,不能像普通函数那样添加外部标签的语法(比如results: Array<Any>这种写法,Swift会误以为你要定义外部标签,而闭包不支持这个)。
你的代码为什么会报错?
你尝试写的这个版本:
func searchForLocation(name: String, completion: @escaping (results: Array<Any>, error: NSError) -> ()) { }
这里的results:和error:被Swift识别为试图给闭包参数添加外部标签,但闭包类型不允许这种语法,所以编译直接失败。
下划线的作用是什么?
你添加的下划线_,其实是显式声明这个闭包参数没有外部参数名——虽然这是闭包的默认行为,但写出来能让代码意图更清晰:告诉任何读代码的人(包括你自己),调用这个闭包的时候不需要写参数标签。
其实你也可以不用下划线,直接写内部参数名,比如:
func searchForLocation(name: String, completion: @escaping (results: [Any], error: NSError?) -> Void) { }
这个代码是可以正常编译的,但要注意:调用闭包的时候绝对不能写results:或error:标签,只能直接传值。比如在函数内部调用闭包时,必须写:
completion(myResults, myError)
而不是completion(results: myResults, error: myError)——这才是下划线真正要强调的点。
为什么苹果的方法看起来不一样?
你提到苹果的相关方法,比如URLSession的dataTask(with:completionHandler:):
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
这里的闭包参数没有写参数名,但这只是苹果的编码风格选择(因为类型已经足够清晰),本质和你的代码逻辑一致。你完全可以给这些闭包参数加上内部名,比如:
func dataTask(with url: URL, completionHandler: @escaping (data: Data?, response: URLResponse?, error: Error?) -> Void) -> URLSessionDataTask
而且苹果的方法里同样不会给闭包参数加外部标签,调用闭包时也不能写参数标签。
总结一下
Swift闭包的设计就是默认不支持外部参数标签,所以你不能像定义普通函数参数那样给闭包参数添加外部标签。下划线只是显式声明这个默认行为,让代码更易读;如果你不想写下划线,直接写内部参数名也是可行的,只要记住调用闭包时不能用参数标签就好。
内容的提问来源于stack exchange,提问作者Supertecnoboff

