理解SwiftUI DSL

493 阅读2分钟

我们来看一段简单的SwiftUI声明

var body = List {

    Text("1")

    Text("2")
    
}

看到这个代码,我一直有个问题,这个代码怎么通过Swift编译的? 为什么有2个返回值。

查看方法声明可以明白

 @MainActor public init(@ViewBuilder content: () -> Content)

是一个ViewBuilder类型的闭包。

{
 Text("1")
 Text("2")
}

再进一步可以发现ViewBuilder被声明成了resultBuilder

@resultBuilder public struct ViewBuilder

这时候,我们就需要好好好理解什么是resultBuilder

ResultBuilder

查看文档可知,ResultBuilder于Swift5.4推出。 源于Swift5.1 Function Builder。

完全依赖Swift Compiler的功劳,我们上面的代码会在Compiler的作用下,转换为:

var body = List {
    let _a = Text("1")

    let _b = Text("2")
    return ViewBuilder.buildBlock(_a, _b)
}

这样看起来是不是就舒服很多了。

但是一个ViewBuilder肯定不是仅仅支持一个多语句的封装返回,肯定还是需要支持一些逻辑控制语句的,那ViewBuilder里面其他那些功能呢?

再回答这个问题之前,我们要来看下ResultBuilder能处理的事情。

ResultBuilder 功能罗列

resultBuilder支持的所有功能

                                   // 1. buildBlock()
let statementBlock = {
    var someVal = 5                // 2. nothing
    "expression statement"         // 3. buildExpression()
    if someVal == 5 {              // 4. buildOptional()
        
    }
    
    if someVal == 5 {              // 5. buildEither
        
    } else if someVal == 6 {
        
    } else {
        
    }
    
    for i in 0...10 {             // 6.buildArray
        "\(i)"
    }
    
    if #available(macOS 13.0, iOS 16.0, *) { // 7. buildLimitedAvailability
        
    }
    
                                   // 8. 最终针对BuildBlock可以再次封装 buildFinalResult
}

再理解完ResutBuilder之后,我们来看下ViewBuilder,我们遇到的一些奇怪的问题。

SwiftUI问题分析

Extra argument in call

List {
    Text("1")
    Text("2")
    Text("3")
    Text("4")
    Text("5")
    Text("6")
    Text("7")
    Text("8")
    Text("9")
    Text("10")
    Text("11") // Extra argument in call
}

简单分析一下 上面的语句在Compiler会被转换成

return ViewBuilder.buildBlock(xxxx)

我们再看一下ViewBuilder的实现。

image.png 我们可以发现buildBlock最多接收10个参数,所以....你只能入参这么多。 处理方法也很简单,可以把多个Text封装在一起。

Closure containing control flow statement cannot be used with result builder 'ViewBuilder'

List {
    for i in 0...10 {
        Text("\(i)")
    }
}

我只想说,为什么一个for...in都实现不了????? for in需要viewBuilder实现buildArray方法,那看这种情况ViewBuilder肯定是没有实现了。我们来扩展一下。 下面的代码就可以正常编译了。

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {

    public static func buildArray<Content>(_ components: [Content]) -> Content where Content : View {
        return components.first!
    }
}
List {
    for i in 0...10 {
        Text("\(i)")
    }
}

引用

github.com/apple/swift…