{"data":{"post":{"title":"SwiftUI 探祕 - SwiftUI 的編程語言本質","subtitle":"","isPublished":true,"createdTime":"2022-03-06T00:00:00.000Z","lastModifiedTime":null,"license":null,"tags":["SwiftUI 探祕","SwiftUI","Swift"],"category":"編程","file":{"childMdx":{"excerpt":"前言 蘋果在 WWDC 2019 向開發者介紹了 SwiftUI。多數人也許會將 SwiftUI 看成又一個如  Flutter  或者  React.js  又或者  Vue.js…","code":{"body":"function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\nconst layoutProps = {};\nreturn class MDXContent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.layout = null;\n  }\n\n  render() {\n    const _this$props = this.props,\n          {\n      components\n    } = _this$props,\n          props = _objectWithoutProperties(_this$props, [\"components\"]);\n\n    return React.createElement(MDXTag, {\n      name: \"wrapper\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"h2\",\n      components: components\n    }, `前言`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `蘋果在 WWDC 2019 向開發者介紹了 SwiftUI。多數人也許會將 SwiftUI 看成又一個如 `, React.createElement(MDXTag, {\n      name: \"em\",\n      components: components,\n      parentName: \"p\"\n    }, `Flutter`), ` 或者 `, React.createElement(MDXTag, {\n      name: \"em\",\n      components: components,\n      parentName: \"p\"\n    }, `React.js`), ` 又或者 `, React.createElement(MDXTag, {\n      name: \"em\",\n      components: components,\n      parentName: \"p\"\n    }, `Vue.js`), ` 這樣踩在聲明式、無狀態 UI 編程潮流浪尖的 UI 框架。雖然 SwiftUI 與上述框架有着非常多的共同點，但是 SwiftUI 從設計到實現上都與上述框架有着本質的不同。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `實際上，相較於是一個編程框架，SwiftUI 更加像是一種編程語言。不相信？讓我來看看一個用「原生」SwiftUI 代碼編寫的斐波那契数列計算程序。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `import SwiftUI\n\nstruct ContentView: View {\n\n    var body: some View {\n        Fibonacci(ordinal: 10)\n    }\n\n}\n\nstruct Fibonacci: View {\n\n    var ordinal: Int\n\n    private var value: Value\n\n    init(ordinal: Int) {\n        self.init(ordinal: ordinal, value: .zero)\n    }\n\n    private init(ordinal: Int, value: Value) {\n        self.ordinal = ordinal\n        self.value = value\n    }\n\n    var body: some View {\n        if ordinal == 0 {\n            Text(\"\\\\(value.description)\")\n        } else {\n            Fibonacci(ordinal: ordinal - 1, value: value.next())\n        }\n    }\n\n    private enum Value: CustomStringConvertible {\n\n        case zero\n\n        case one\n\n        case more(last: Int, current: Int)\n\n        func next() -> Value {\n            switch self {\n            case .zero: return .one\n            case .one:  return .more(last: 0, current: 1)\n            case .more(let last, let current):\n                return .more(last: current, current: last + current)\n            }\n        }\n\n        var description: String {\n            switch self {\n            case .zero:                         return \"0\"\n            case .one:                          return \"1\"\n            case .more(let last, let current):  return \"\\\\(last + current)\"\n            }\n        }\n\n    }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `然後通過添加以下兩行代碼，我們可以在 Swift Playground 預覽上面的代碼。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `import PlaygroundSupport\n\nPlaygroundPage.current.setLiveView(ContentView())\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"figure\",\n      components: components,\n      parentName: \"p\"\n    }, `\n    `, React.createElement(MDXTag, {\n      name: \"a\",\n      components: components,\n      parentName: \"figure\",\n      props: {\n        \"href\": \"/static/c41ed74d5550f47ced8c9b7efbde17e8/94001/fibonacci-number-on-swift-playground.webp\"\n      }\n    }, React.createElement(MDXTag, {\n      name: \"picture\",\n      components: components,\n      parentName: \"a\"\n    }, `\n  `, React.createElement(MDXTag, {\n      name: \"source\",\n      components: components,\n      parentName: \"picture\",\n      props: {\n        \"src\": \"/static/c41ed74d5550f47ced8c9b7efbde17e8/4ae7e/fibonacci-number-on-swift-playground.jpeg\",\n        \"srcSet\": [\"/static/c41ed74d5550f47ced8c9b7efbde17e8/aeae1/fibonacci-number-on-swift-playground.jpeg 178w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/3465b/fibonacci-number-on-swift-playground.jpeg 356w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/4ae7e/fibonacci-number-on-swift-playground.jpeg 712w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/01d22/fibonacci-number-on-swift-playground.jpeg 1068w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/accbd/fibonacci-number-on-swift-playground.jpeg 1424w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/c44b2/fibonacci-number-on-swift-playground.jpeg 2136w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/ac69a/fibonacci-number-on-swift-playground.jpeg 2732w\"],\n        \"sizes\": \"(max-width: 712px) 100vw, 712px\"\n      }\n    }), React.createElement(MDXTag, {\n      name: \"source\",\n      components: components,\n      parentName: \"picture\",\n      props: {\n        \"src\": \"/static/c41ed74d5550f47ced8c9b7efbde17e8/943f6/fibonacci-number-on-swift-playground.webp\",\n        \"srcSet\": [\"/static/c41ed74d5550f47ced8c9b7efbde17e8/b1d2a/fibonacci-number-on-swift-playground.webp 178w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/c91cd/fibonacci-number-on-swift-playground.webp 356w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/943f6/fibonacci-number-on-swift-playground.webp 712w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/8a6dd/fibonacci-number-on-swift-playground.webp 1068w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/fee22/fibonacci-number-on-swift-playground.webp 1424w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/fbb66/fibonacci-number-on-swift-playground.webp 2136w\", \"/static/c41ed74d5550f47ced8c9b7efbde17e8/94001/fibonacci-number-on-swift-playground.webp 2732w\"],\n        \"sizes\": \"(max-width: 712px) 100vw, 712px\"\n      }\n    }), `\n  `, React.createElement(MDXTag, {\n      name: \"img\",\n      components: components,\n      parentName: \"picture\",\n      props: {\n        \"src\": \"/static/c41ed74d5550f47ced8c9b7efbde17e8/94001/fibonacci-number-on-swift-playground.webp\",\n        \"alt\": \"在 SwiftUI Playground 中計算斐波那契数列\",\n        \"title\": \"在 SwiftUI Playground 中計算斐波那契数列\",\n        \"width\": 712,\n        \"height\": 534,\n        \"loading\": \"lazy\"\n      }\n    }))), `\n    `, React.createElement(MDXTag, {\n      name: \"figcaption\",\n      components: components,\n      parentName: \"figure\"\n    }, `\n        `, React.createElement(MDXTag, {\n      name: \"span\",\n      components: components,\n      parentName: \"figcaption\"\n    }, `\n            在 SwiftUI Playground 中計算斐波那契数列\n        `), `\n    `))), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `如你所見，我們使用「原生」 SwiftUI 代碼計算出了斐波那契数列。但是可以使用來自 SwiftUI 的東西寫出一個程序並不意味着 SwiftUI 是一門編程語言。只有當 SwiftUI 爲這個程序提供了真正的「語言結構」時，我們才能說 SwiftUI 是一門編程語言。`), React.createElement(MDXTag, {\n      name: \"h2\",\n      components: components\n    }, `View 協議中未被解釋的部分`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `要理解 SwiftUI 的語言結構，我們必須首先理解 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議中沒有被解釋過的部分。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `基本上，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議會如下所示：`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `public protocol View {\n\n    associatedtype Body: View\n\n    @ViewBuilder\n    var body: Body { get }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `我們可以看到，一旦一個類型遵從了 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議，它的實例將擁有一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 屬性。同時這個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 屬性也將返回一個遵從 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議的實例。這將導致一個有趣的現象：如果我們獲得了一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), `，那麼我們將又能獲得這個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), `。因爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 也是一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例，所以我們可以繼續獲得它的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), `。理論上，我們可以無限重複這個模式。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"figure\",\n      components: components,\n      parentName: \"p\"\n    }, `\n    `, React.createElement(MDXTag, {\n      name: \"a\",\n      components: components,\n      parentName: \"figure\",\n      props: {\n        \"href\": \"/static/7db2c008a266dea684c261a8ffdfa58e/d4e8c/execution-pattern.png\"\n      }\n    }, React.createElement(MDXTag, {\n      name: \"img\",\n      components: components,\n      parentName: \"a\",\n      props: {\n        \"title\": \"SwiftUI 的執行模式\",\n        \"alt\": \"SwiftUI 的執行模式\",\n        \"src\": \"/static/7db2c008a266dea684c261a8ffdfa58e/d4e8c/execution-pattern.png\",\n        \"srcSet\": [\"/static/7db2c008a266dea684c261a8ffdfa58e/d4e8c/execution-pattern.png 1x\", \"/static/8904106481aa27eadbe86cb9c4a0f4af/4c6f7/execution-pattern%402x.png 2x\", \"/static/3397ae046d0c0559183c372af0b01c63/876d1/execution-pattern%403x.png 3x\"],\n        \"loading\": \"lazy\"\n      }\n    })), `\n    `, React.createElement(MDXTag, {\n      name: \"figcaption\",\n      components: components,\n      parentName: \"figure\"\n    }, `\n        `, React.createElement(MDXTag, {\n      name: \"span\",\n      components: components,\n      parentName: \"figcaption\"\n    }, `\n            SwiftUI 的執行模式\n        `), `\n    `))), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `是的。你也許已經意識到了 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議的設計是遞歸的。實際上，這個遞歸的設計提供了 SwiftUI 的基本語言結構。而這個基本語言結構引入了一種可以遞歸的執行模式。SwiftUI 的內部機制可以持連不斷地續獲得 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 直到遞歸結束。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `但是什麼時候遞歸結束呢？`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `SwiftUI 擴展了標準庫中的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), ` 類型以遵從 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 協議。在這個擴展中，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 同時會返回 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), `。所以我們可以知道這就是遞歸的終點。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `extension Never: View { }\n\nextension Never {\n\n    public typealias Body = Never\n\n    public var body: Never { get }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `在 SwiftUI 的內部實現中，一旦一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 是 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), ` 類型的，SwiftUI 將停止獲取這個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), `，轉而切換到這個視圖的`, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `內建`), `邏輯。如果這個視圖的`, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `內建`), `邏輯不會將執行轉交給其他 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例，那麼遞歸將終止。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `SwiftUI 帶來了很多 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 是 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), ` 類型的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 類型。由於這類 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 類型擁有其`, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `內建`), `的邏輯，他們也被成爲`, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `內建視圖`), `。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `在我上面展示的斐波那契数列的例子中， 因爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Text`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 是 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Never`), ` 類型的，同時 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Text`), ` 的內建邏輯不會將程序執行轉交給其他實例，程序的遞歸將終止在 SwiftUI 執行到 Fibonacci `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 中的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Text`), ` 視圖時。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `extension Text : View {\n\n    public typealias Body = Never\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"h2\",\n      components: components\n    }, `其他語言結構`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `在前面的描述中，我將 SwiftUI 執行模式中遞歸結束的條件限縮到了：`), React.createElement(MDXTag, {\n      name: \"blockquote\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"p\",\n      components: components,\n      parentName: \"blockquote\"\n    }, `一個不會將程序執行轉交給其他 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例的`, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `內建`), `視圖`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `從這個描述中你也許可以推斷，SwiftUI 中存在內建視圖會將程序執行邏輯轉交給其他 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `View`), ` 實例。沒錯，確實如此。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `在這裏我將介紹其中的一個以幫助大家理解上面斐波那契数列範例中 SwiftUI 所提供的語言結構：`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 中 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if..else...`), ` 語句背後的語言結構 —— `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `_ConditionalContent`), `。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `struct Fibonacci: View {\n\n    var body: some View {\n        // \\`if...else...\\` 語句將最終聲稱一個 \n        // \\`_ConditionalContent\\` 實例\n        if ordinal == 0 {\n            // ...\n        } else {\n            // ...\n        }\n    }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `_ConditionalContent`), ` 並沒有在蘋果的公開文檔中暴露出來. 但是我們可以在 SwiftUI 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `swiftinterface`), ` 文件中找到它：`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `public struct _ConditionalContent<TrueContent, FalseContent> {\n\n  internal enum Storage {\n    case trueContent(TrueContent)\n    case falseContent(FalseContent)\n  }\n\n  internal let storage: Storage\n\n}\n\nextension ConditionalContent : View where TrueContent : View, FalseContent : View {\n\n  public typealias Body = Never\n\n  internal init(storage: Storage)\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"blockquote\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"p\",\n      components: components,\n      parentName: \"blockquote\"\n    }, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `swiftinterface`), ` 文件之於 Swift module 就像頭文件之於 clang module。`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `同時，SwiftUI 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `ViewBuilder`), ` 擁有一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `extension`), `。裏面的內容告訴我們，當我們在一個被 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `@ViewBuiler`), ` 標記的函數內使用 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if...else...`), ` 語句時，Swift 編譯器將會在編譯時生成一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `_ConditionalContent`), ` 實例來包裹 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if...else...`), ` 兩側分支內的內容 —— 當 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if`), ` 後的表達式爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `true`), ` 的時候，生成代碼喚起第一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `buildEither`), ` 函數；爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `false`), ` 時，生成代碼喚起第二個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `buildEither`), ` 函數。`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `extension ViewBuilder {\n\n  public static func buildEither<TrueContent : View, FalseContent : View>(first: TrueContent) -> ConditionalContent<TrueContent, FalseContent> {\n    .init(storage: .trueContent(first))\n  }\n\n  public static func buildEither<TrueContent : View, FalseContent : View>(second: FalseContent) -> ConditionalContent<TrueContent, FalseContent> {\n   .init(storage: .falseContent(second))\n  }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `通過將我們在 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `ViewBuilder`), ` `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `extension`), ` 中所發現的以及 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `_ConditionalContent`), ` 本身的設計相結合，我們而可以推導出 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `_ConditionalContent`), ` 能夠攜帶兩種實例 —— 一種是 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if...else`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `ture`), ` 分支下的內容，一種是 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `false`), ` 分支下的內容。`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `舉個例子，在斐波那契数列範例中，當 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `if`), ` 之後的表達式爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `true`), ` 時，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 等於如下代碼：`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `struct Fibonacci: View {\n\n    var body: some View {\n        _ConditionalContent(storage: .trueContent(Text(...)))\n    }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `當表達式爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `false`), ` 時，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 等於如下代碼：`), React.createElement(MDXTag, {\n      name: \"pre\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"code\",\n      components: components,\n      parentName: \"pre\",\n      props: {\n        \"className\": \"language-swift\"\n      }\n    }, `struct Fibonacci: View {\n\n    var body: some View {\n        _ConditionalContent(storage: .falseContent(Fibonacci(...)))\n    }\n\n}\n`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"strong\",\n      components: components,\n      parentName: \"p\"\n    }, `然後 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"strong\"\n    }, `_ConditionContent`), ` 的內建邏輯最終會將程序執行轉交給它 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"strong\"\n    }, `storage`), ` 中存儲的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"strong\"\n    }, `View`), ` 實例`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `所以我們可以知道，除非 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `ordinal`), ` 爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `0`), `，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 將永遠遞歸地生成另一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), `。然後當 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `ordinal`), ` 爲 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `0`), ` 時，`, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `body`), ` 將生成一個 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Text`), ` 視圖以渲染 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"p\"\n    }, `Fibonacci`), ` 的最終輸出結果。`), React.createElement(MDXTag, {\n      name: \"h2\",\n      components: components\n    }, `結論`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `通過之前的分析，我們可以清楚地知道 SwiftUI 確實爲上述斐波那契数列範例的執行過程提供了關鍵的語言結構，他們包括：`), React.createElement(MDXTag, {\n      name: \"ul\",\n      components: components\n    }, React.createElement(MDXTag, {\n      name: \"li\",\n      components: components,\n      parentName: \"ul\"\n    }, `一個帶來可遞歸執行模式的 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"li\"\n    }, `View`), ` 協議。`), React.createElement(MDXTag, {\n      name: \"li\",\n      components: components,\n      parentName: \"ul\"\n    }, `一個遵從 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"li\"\n    }, `View`), ` 協議的類型 `, React.createElement(MDXTag, {\n      name: \"inlineCode\",\n      components: components,\n      parentName: \"li\"\n    }, `_ConditionalContent`), `。它負責處理條件分支。`)), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `這就很像一門真實的編程語言了。所以我們可以說相較是一個 UI 框架，SwiftUI 更像是一門語言。`), React.createElement(MDXTag, {\n      name: \"h2\",\n      components: components\n    }, `一個形式化的視角`), React.createElement(MDXTag, {\n      name: \"p\",\n      components: components\n    }, `實際上，這個斐波那契数列的範例展現了 SwiftUI 是圖靈完備的：如果一個過程可以重新進入它自己，並且這個過程可以無限改變它的行爲，那麼這個過程背後的語言就是圖靈完備的。`));\n  }\n\n}\nMDXContent.isMDXComponent = true;","scope":""},"headings":[{"value":"前言","depth":2},{"value":"View 協議中未被解釋的部分","depth":2},{"value":"其他語言結構","depth":2},{"value":"結論","depth":2},{"value":"一個形式化的視角","depth":2}]}}},"earlierPostExcerpt":null,"laterPostExcerpt":{"slug":"/post/2022/08/using-functional-binding-to-observe-in-swiftui-19a8","title":"在 SwiftUI 中使用函數式 Binding 實現觀察者模式","subtitle":"","createdTime":"2022-08-19T00:00:00.000Z","tags":["SwiftUI","Binding","Swift","觀察者模式"],"category":"編程","file":{"childMdx":{"excerpt":"故事 這週，我的同事問了我一個問題：在 SwiftUI 中怎麼觀察用戶對  Picker  的選擇行爲？ 這是一個來自真實業務的問題，所以我覺得值得我花費時間去解決它。 範例代碼如下所示，然後我的同事想觀察用戶對  Picker  候選項的選擇行爲。 分析 但是，「觀察」本身的意義可能會隨着上下文變動而變動： 它可以表示用戶在  Picker  上放下手指的那一刻。 它可以表示用戶在  Picker  上擡起手指的那一刻。 它可以表示  Picker  對  $selection  進行值變更的那一刻。 上述每一項都將導致不同的最終解決方案。 因爲 SwiftUI 控件可以使用 style…"}}}},"pageContext":{"postId":"1488d9d1-b04a-52ec-bff4-f6a0e73301f8","earlierPostId":null,"laterPostId":"1c2f780d-e035-5c3e-ba96-254e56803c75"}}