也许是作为争议最大的特性之一,多尾闭包这个特性被纳入 Swift 5.3。为什么会有那么大的争议呢?听我慢慢道来。
调用单个尾闭包的函数时有一种精简的写法:省去这个尾闭包的标签、并且把闭包放在圆括号外面。以下两种写法等价。
UIView.animate(withDuration: 0.25, animations: {
// animation code
})
// 单个尾闭包的精简写法
UIView.animate(withDuration: 0.25) {
// animation code
}
然而,Swift 5.3 之前如果有多个尾闭包的话,也只有最后一个闭包能被写成精简形式,这种写法苹果觉得不是太好看,因为一个闭包在圆括号内,另一个在外面。因此苹果的推荐做法是,碰到这种需要还是换成传统的写法吧。
// 写法 1(单尾闭包精简写法)
UIView.animate(withDuration: 0.25, animations: {
// animation code
}) { completed in
// completion code
}
// 写法 2(传统写法)
UIView.animate(withDuration: 0.25, animations: {
// animation code
}, completion: { completed in
// completion code
})
正因如此,给这次的争议埋下了隐患,因为这只是建议,并没有什么强制的措施,甚至编译器也不会给警告的。事实上,在 Xcode 12 之前,直接在最后一个闭包的代码提示上按回车,代码会被自动切换成第一种的形式。因此,第一种写法有非常多的存量代码。
Swift 5.3 的时候,苹果“重新思考”了多尾闭包的场景,它给出了一种新的调用写法:
// 写法 3(多尾闭包精简写法)
UIView.animate(withDuration: 0.25) {
// animation code
} completion: { completed in
// completion code
}
这种新的写法,把最后的连续几个闭包参数都算作是尾闭包了,这些闭包现在都可以放在圆括号外面了,显得清爽了很多。
但是,这些尾闭包中的第一个闭包被强制省略,可以看到这里的 animations
标签被强制省略,注意是强制哦!加上了是编译不过的。苹果给出的解释是:如果允许第一个尾闭包加上标签,那么开发者需要考虑是加好还是不加好,你看这会导致代码风格不一致,那么干脆禁了吧。
呵呵。一个多尾闭包的情况现在都有三种合法的写法了,你不在考虑增加第三种写法的时候考虑以下是不是会让开发者多一种选择的为难,却贴心的考虑到了“我就是要”增加的第三种写法里面让开发者少一点为难,谢谢你啊。
这个多尾闭包还有个特性是,除了第一个尾闭包不配拥有姓名之外,其余的尾闭包都得有姓名(标签)。所以搞笑的是:同样作为合法的写法,第一种写法省略了completion
标签,而第三种写法省略了animations
标签。这不是找骂么,对于 API 的设计者来说,一个清晰合理的调用是函数设计考量的重要一点,之前我要想到的是多闭包的情况下最后一个标签有可能被用户调用时候省略,但是现在你说,不不,第一个才会被强制省略?
苹果怎么办呢?只好在 API 设计规范里面说,大家现在开始要注意第一个尾闭包标签会被省略,要给之后的尾闭包起个好的标签名哦。
然而,这又解决了什么问题呢?考虑到源代码的兼容性,Swift 永远不可能去掉第一种调用方法,用户依旧可以使用第一种写法来使用多尾闭包的函数,所以上面所述的问题也会永远存在。苹果可以做的不过是在今后新的多尾闭包写法铺开之后,悄咪咪地给第一种写法加一些 warning 和自动转换的功能吧。
引起那么大争论,我想苹果现在很后悔为什么当初就不禁止第一种写法。如果当初禁止第一种写法,现在的改动应该会是一片叫好吧。
那么又是什么原因让苹果不惜背上被开发者喷的代价也要把这个特性加上呢?
哎,不是因为 SwiftUI 又是为什么呢?
苹果铺垫了那么多,说是解决一个通用的多尾闭包的问题,最后才把真正的意图亮出来,为了 SwiftUI 的 API。
嘤嘤嘤,没有多尾闭包,人家只能把 Section
设计成这个样子啦!
init(header: Parent, footer: Footer, content: () -> Content)
// 调用
Section(
header: ...,
footer: ...
) {
// content
}
这里只有 content
才用上了 ViewBuilder
修饰的闭包 ViewBuilder 介绍,header
和 footer
只能传入实例,导致用户必须写一个新的类型才行。而理想情况下,这三个参数都应该是 ViewBuilder
修饰的闭包:
init(content: () -> Content, header:() -> Parent, footer: () -> Footer)
// 调用
Section {
// content
} header: {
...
} footer: {
...
}
并且 content
标签是会被默认省略的,所以应当放到第一个。
我们看一个新的 SwiftUI 的组件再来体会下:
struct Gauge
终于可以撒开了欢地用尾闭包了呢!
结语
一门开源语言的进化动力,不能仅仅来源于一个私有框架。在去年为了 SwiftUI 引入大量新特性,并且最为关键的 FunctionBuilder 至今没有通过语言特性评审的情况下,苹果今年又一意孤行地引入多尾闭包这个争议特性,可谓是走了一条与开源精神背道而驰的路。
Swift 今年号称要推广到更多的平台,例如 Windows,这么维护开发者关系的话,很难说到时候有多少人会站在你这一边。