var body: some View {
HStack(alignment: .leading, spacing: 10) {
Text("Hello Swift")
Text("Hello SwiftUI")
Text("Hello SwiftUI Study")
}
}
不知道大家有沒有問過自己,為啥在SwiftUI中可以如上面這樣寫墨闲?
實(shí)際上可以像上面這樣去使用今妄,得益于兩個(gè)特性:Swift的尾隨閉包 & FunctionBuilder。
尾隨閉包
我們查看HStack的定義鸳碧,發(fā)現(xiàn)它的init初始化函數(shù)定義如下:
@inlinable public init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, content: () -> Content)
在init函數(shù)的定義中盾鳞,最后一項(xiàng)是一個(gè)閉包content: () -> Content ,在Swift語法中我們知道杆兵,如果函數(shù)的最后一個(gè)參數(shù)是一個(gè)閉包雁仲,那么可以把這個(gè)閉包提到圓括號(hào)外面,那上面的代碼本來應(yīng)該是這樣:
var body: some View {
HStack(alignment: .leading, spacing: 10, content: {
Text("Hello Swift")
Text("Hello SwiftUI")
Text("Hello SwiftUI Study")
})
}
這就是Swift中的其中一種閉包:尾隨閉包琐脏。
另外在Swift中攒砖,如果某個(gè)函數(shù)只有一個(gè)參數(shù),且這個(gè)參數(shù)是一個(gè)閉包或者這個(gè)閉包參數(shù)之前參數(shù)提供了默認(rèn)值的話日裙,可以完全省略圓括號(hào)吹艇。在上面init函數(shù)的定義中我們看到尾隨閉包之前的參數(shù)都提供了默認(rèn)值,所以SwiftUI中也有這樣的寫法:
var body: some View {
HStack {
Text("Hello Swift")
Text("Hello SwiftUI")
Text("Hello SwiftUI Study")
}
}
FunctionBuilder
但你肯定還會(huì)有疑問昂拂,上面init函數(shù)的尾隨閉包受神,明明是有返回值Content,可是在使用HStack的地方格侯,為什么沒有任何的return呢鼻听?而且如果你嘗試下面這樣的寫法财著,編譯器是要報(bào)錯(cuò)的,所以這個(gè)閉包可能不是一個(gè)簡單的閉包:
var body: some View {
HStack {
let a = 1
print(a)
Text("Hello Swift")
Text("Hello SwiftUI")
Text("Hello SwiftUI Study")
}
}
其實(shí)上面init函數(shù)的定義撑碴,我們省略了一些內(nèi)容撑教,原始是這樣的:
@inlinable public init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
沒錯(cuò),就是多了一個(gè)@ViewBuilder修飾閉包醉拓,那我們就知道一定是這個(gè)家伙讓我們的閉包可以沒有返回值伟姐,它是何方神圣,會(huì)擁有此等魔力亿卤?一起探究一下愤兵!
首先進(jìn)到ViewBuilder的定義,可以看到ViewBuilder其實(shí)是一個(gè)struct:
@_functionBuilder public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
/// unmodified.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
我們注意到排吴,ViewBuilder本身又是被@_functionBuilder所標(biāo)記的秆乳,這就是我們的主角了。這個(gè)語法特性叫Function Builders钻哩。
這是蘋果為SwiftUI直接修改了Swift的編譯器矫夷,還沒有被正式添加到Swift語法中,所以只有在SwiftUI中使用憋槐。
@_functionBuilder作為標(biāo)記双藕,可以作為各種類型的自定義Attribute,比如函數(shù)阳仔,計(jì)算型屬性忧陪,以及上面提到的作為參數(shù)的函數(shù)(閉包),使用@_functionBuilder標(biāo)記之后近范,會(huì)修改語法樹嘶摊,針對(duì)不同的類型會(huì)有不同的轉(zhuǎn)換:
作為函數(shù)的標(biāo)記,會(huì)轉(zhuǎn)化函數(shù)的實(shí)現(xiàn)
作為計(jì)算型屬性的標(biāo)記评矩,會(huì)轉(zhuǎn)化屬性的getter實(shí)現(xiàn)
作為閉包的標(biāo)記叶堆,會(huì)轉(zhuǎn)換閉包內(nèi)的實(shí)現(xiàn)
再回到上面的ViewBuilder,使用@_functionBuilder標(biāo)記之后斥杜,會(huì)轉(zhuǎn)換閉包的實(shí)現(xiàn)為ViewBuilder的實(shí)現(xiàn),在之前的文章中也介紹過虱颗,ViewBuilder中實(shí)現(xiàn)了1到10個(gè)View的buildBlock:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
/// through unmodified.
public static func buildBlock<Content>(_ content: Content)
-> Content where Content : View
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1)
-> TupleView<(C0, C1)> where C0 : View, C1 : View
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2)
-> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View
// ...
}
于是
HStack {
Text("Hello Swift")
Text("Hello SwiftUI")
Text("Hello SwiftUI Study")
}
相當(dāng)于
HStack {
ViewBuilder.buildBlock(
Text("Hello Swift"),
Text("Hello SwiftUI")蔗喂,
Text("Hello SwiftUI Study")
)
}
在ViewBuilder中還實(shí)現(xiàn)了返回條件內(nèi)容的函數(shù):
extension ViewBuilder {
/// Provides support for "if" statements in multi-statement closures, producing
/// ConditionalContent for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures,
/// producing ConditionalContent for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
}
這些函數(shù)提供了條件處理的實(shí)現(xiàn):
var body: some View {
HStack(alignment: .leading) {
if true {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
最終會(huì)被轉(zhuǎn)換為
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(
true ? ViewBuilder.buildEither(first: Text("Hello World!"))
: ViewBuilder.buildEither(second: Text("Goodbye World!")),
Text("Something else")
)
}
}
怎么樣忘渔,是不是挺神奇?其實(shí)Funcation builder這個(gè)語言特性缰儿,在我們平時(shí)用不到畦粮,但是在寫DSL時(shí)就會(huì)非常有用,感興趣的同學(xué)可以去看看相關(guān)實(shí)現(xiàn)哦。