Upgrade to Pro — share decks privately, control downloads, hide ads and more …

20分でわかる!速習resultBuilder(iOSDC 2022)

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for たまねぎ たまねぎ
September 12, 2022

20分でわかる!速習resultBuilder(iOSDC 2022)

Avatar for たまねぎ

たまねぎ

September 12, 2022
Tweet

More Decks by たまねぎ

Other Decks in Technology

Transcript

  1. ɹ ɹ 自己紹介 Profile { Name("ͨ·Ͷ͗") Twitter("@_chocoyama") Work { Company("hey

    inc") Product("STORES Regi") Role("Mobile Engineer") } Skill { SwiftUI() Flutter() Compose() } }
  2. ɹ ɹ どこで使われている? let word = OneOrMore(.word) let emailPattern =

    Regex { Capture { ZeroOrMore { word "." } word } "@" Capture { word OneOrMore { "." word } } } struct AlbumDetail: View { var album: Album var body: some View { List(album.songs) { song in HStack { Image(album.cover) VStack(alignment: .leading) { Text(song.title) Text(song.artist.name) .foregroundStyle(.secondary) } } } } } ViewBuilder RegexComponentBuilder
  3. ɹ ɹ let word = OneOrMore(.word) let emailPattern = Regex

    { Capture { ZeroOrMore { word "." } word } "@" Capture { word OneOrMore { "." word } } } struct AlbumDetail: View { var album: Album var body: some View { List(album.songs) { song in HStack { Image(album.cover) VStack(alignment: .leading) { Text(song.title) Text(song.artist.name) .foregroundStyle(.secondary) } } } } } どこで使われている? ViewBuilder RegexComponentBuilder
  4. ɹ ɹ 何ができる? 1.値の列挙にカンマが必要ない 2.if文やfor文などの制御構文も利用可能 @ArrayBuilder func someFunction2() -> [Int]

    { 1 2 if true { 3 } for i in (4...6) { i } } ಉ݁͡Ռ͕ಘΒΕΔ = [1, 2, 3, 4, 5, 6] func someFunction1() -> [Int] { var result = [ 1, 2 ] if true { result.append(3) } for i in (4...6) { result.append(i) } return result }
  5. ɹ ɹ なぜ必要? struct AlbumDetail: View { var album: Album

    var body: some View { List(album.songs) { song in HStack { Image(album.cover) VStack(alignment: .leading) { Text(song.title) Text(song.artist.name) .foregroundStyle(.secondary) } } } } }
  6. ɹ ɹ resultBuilderを使わずに同じことを実現する場合 struct AlbumDetail: View { var album: Album

    var body: some View { List(album.songs) { song in HStack { Image(album.cover) VStack(alignment: .leading) { Text(song.title) Text(song.artist.name) .foregroundStyle(.secondary) } } } } } struct AlbumDetail: View { var album: Album var body: some View { var items: [View] = [] for song in album.songs { let hStack = HStack( contents: [ Image(album.cover), VStack( alignment: .leading, contents [ Text(song.title), Text(song.artist.name) .foregroundStyle(.secondary) ] ) ] ) items.append(hStack) } return List(contents: items) } } ※ ͜ͷίʔυ͸͋͘·ͰΠϝʔδͰ͢
  7. ɹ ɹ もしresultBuilderを使わずに同じことを実現すると • 制御文や変数宣言でコードが肥大化 • 階層構造が複雑で、結果が想像しづらい • 変更コストが大きい struct

    AlbumDetail: View { var album: Album var body: some View { var items: [View] = [] for song in album.songs { let hStack = HStack( contents: [ Image(album.cover), VStack( alignment: .leading, contents [ Text(song.title), Text(song.artist.name) .foregroundStyle(.secondary) ] ) ] ) items.append(hStack) } return List(contents: items) } } ※ ͜ͷίʔυ͸͋͘·ͰΠϝʔδͰ͢
  8. ɹ ɹ 例えばこういう時 let mas = NSMutableAttributedString(string: “") mas.append(NSAttributedString( string:

    "Hello world", attributes: [ .font: UIFont.systemFont(ofSize: 24), .foregroundColor: UIColor.red ] )) mas.append(NSAttributedString( string: "\n" )) mas.append(NSAttributedString( string: "with Swift", attributes: [ .font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.orange ] ))
  9. ɹ ɹ • 要素指定と関係ない記述が多い • 構造がわかりづらい • 変更による修正量が多い 例えばこういう時 let

    mas = NSMutableAttributedString(string: “") mas.append(NSAttributedString( string: "Hello world", attributes: [ .font: UIFont.systemFont(ofSize: 24), .foregroundColor: UIColor.red ] )) mas.append(NSAttributedString( string: "\n" )) mas.append(NSAttributedString( string: "with Swift", attributes: [ .font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.orange ] ))
  10. ɹ ɹ 例えばこういう時 let mas = NSMutableAttributedString(string: “") mas.append(NSAttributedString( string:

    "Hello world", attributes: [ .font: UIFont.systemFont(ofSize: 24), .foregroundColor: UIColor.red ] )) mas.append(NSAttributedString( string: "\n" )) mas.append(NSAttributedString( string: "with Swift", attributes: [ .font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.orange ] )) let attributedString = NSAttributedString { AText("Hello world") .font(.systemFont(ofSize: 24)) .foregroundColor(.red) LineBreak() AText("with Swift") .font(.systemFont(ofSize: 20)) .foregroundColor(.orange) } • 要素指定と関係ない記述が多い • 構造がわかりづらい • 変更による修正量が多い • 要素指定以外の記述が少ない • 構造がわかりやすい • 変更による修正量が少ない // https://github.com/ethanhuang13/NSAttributedStringBuilder
  11. ɹ ɹ awesome-result-builders https://github.com/carson-katri/awesome-result-builders // Data Data { [UInt8(0)] UInt8(1)

    Int8(2) "\u{03}" Int16(1284) if dataClause { CustomData() } } // Parsing let capture = HTML { TryCapture("#hello") { (element: HTMLElement?) -> String? in return element?.textContent } Local("#group") { Capture("h1", transform: \.textContent) Capture("h2", transform: \.textContent) } } // Networking Request { Url("https://jsonplaceholder.typicode.com/posts") Method(.post) Header.ContentType(.json) Body(Json([ "title": "foo", "body": "bar", "usedId": 1 ]).stringified) } // GraphQL Operation(.query) { Add(\.country, alias: "canada") { Add(\.name) Add(\.continent) { Add(\.name) } }.code("CA") }
  12. ɹ ɹ 作る @ArrayBuilder func someFunction() -> [Int] { 1

    2 3 } someFunction() // [1, 2, 3] @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } }
  13. ɹ ɹ @resultBuilder struct ArrayBuilder { static func buildBlock(_ components:

    Int...) -> [Int] { components } } 作る resultBuilderアノテーションの付与
  14. ɹ ɹ @resultBuilder struct ArrayBuilder { static func buildBlock(_ components:

    Int...) -> [Int] { components } } 作る buildBlock関数を定義
  15. ɹ ɹ @resultBuilder struct ArrayBuilder { static func buildBlock(_ components:

    Int...) -> [Int] { components } } 作る 引数として受け取れる型 & 返却値の型を指定
  16. ɹ ɹ @resultBuilder struct ArrayBuilder { static func buildBlock(_ components:

    Int...) -> [Int] { components } } 作る インプット値をアウトプット値に変換
  17. ɹ ɹ @ArrayBuilder func someFunction() -> [Int] { 1 2

    3 } someFunction() // [1, 2, 3] @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } } 使う 作成したクラス名をアノテーションでfunc, var, etcに付与する
  18. ɹ ɹ @ArrayBuilder func someFunction() -> [Int] { 1 2

    3 } someFunction() // [1, 2, 3] @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } } 使う buildBlockの引数の型に対応した値を列挙
  19. ɹ ɹ @ArrayBuilder func someFunction() -> [Int] { 1 2

    3 } someFunction() // [1, 2, 3] @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } } 使う 指定された返却値の型の値が返される
  20. ɹ ɹ @ArrayBuilder func someFunction() -> [Int] { 1 2

    3 } someFunction() // [2, 4, 6] @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components.map { $0 * 2 } } } 使う 任意の処理を挟むことができる
  21. ɹ ɹ 制御構文への対応 @ArrayBuilder func function1() -> [Int] { //

    Closure containing control flow statement cannot be used with result builder 'ArrayBuilder' if true { 1 } } @ArrayBuilder func function2() -> [Int] { // Closure containing control flow statement cannot be used with result builder 'ArrayBuilder' for i in (2..<10) { i } } If,forͳͲͷར༻ʹ͸ɺରԠ͢ΔbuildXXXؔ਺Λ࣮૷͢Δඞཁ͕͋Δ
  22. ɹ ɹ buildXXX関数 ؔ਺ ݺͼग़͞ΕΔՕॴ buildBlock ϒϩοΫ͝ͱ buildExpression ࣜ͝ͱ buildOptional

    elseͷͳ͍෼ذ buildEither elseͷ͋Δ෼ذ switchʹΑΔ෼ذ buildArray ܁Γฦ͠ buildFinalResult ݁Ռͷ஋ͷ׬੒ buildLimitedAvailability #availableʹΑΔ෼ذ buildPartialResult ϒϩοΫͷߦ͝ͱ ※ ΦʔόʔϩʔυՄೳ
  23. ɹ ɹ buildBlock func someFunction() -> [Int] { let v0

    = 1 let v1 = 2 let v2 = 3 return ArrayBuilder.buildBlock(v0, v1, v2) }
  24. ɹ ɹ buildBlock func someFunction() -> [Int] { let v0

    = 1 let v1 = 2 let v2 = 3 return ArrayBuilder.buildBlock(v0, v1, v2) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } }
  25. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1 if Bool.random() { 2 3 } return ArrayBuilder.buildBlock(v0, v1) }
  26. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1 if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) } return ArrayBuilder.buildBlock(v0, v1) }
  27. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1 if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) } return ArrayBuilder.buildBlock(v0, v1) } ؔ਺ ݺͼग़͞ΕΔՕॴ buildOptional elseͷͳ͍෼ذ
  28. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1 if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) }
  29. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1: [Int] if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } }
  30. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1: [Int] if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } } Error!
  31. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1: [Int] if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } } Int [Int]
  32. ɹ ɹ 分岐への対応 func someFunction() -> [Int] { let v0:

    Int = 1 let v1: [Int] if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } } ᶃ v0Λ[Int]ʹม׵ͯ͠ܕ͕ἧ͏Α͏ʹ͢Δ ᶄ buildBlockͰ[Int]ͷՄม௕Ҿ਺Λ౉ͤΔΑ͏ʹ͢Δ
  33. ɹ ɹ ① v0を[Int]に変換して型が揃うようにする func someFunction() -> [Int] { let

    v0: Int = 1 let v1: [Int] if Bool.random() { let v1_0 = 2 let v1_1 = 3 let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } } ᶃ v0Λ[Int]ʹม׵ͯ͠ܕ͕ἧ͏Α͏ʹ͢Δ
  34. ɹ ɹ ① v0を[Int]に変換して型が揃うようにする func someFunction() -> [Int] { let

    v0: [Int] = ArrayBuilder.buildExpression(expression: 1) let v1: [Int] if Bool.random() { let v1_0: [Int] = ArrayBuilder.buildExpression(expression: 2) let v1_1: [Int] = ArrayBuilder.buildExpression(expression: 3) let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildExpression(_ expression: Int) -> [Int] { [expression] } static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { ؔ਺ ݺͼग़͞ΕΔՕॴ buildExpression ࣜ͝ͱ ᶃ v0Λ[Int]ʹม׵ͯ͠ܕ͕ἧ͏Α͏ʹ͢Δ
  35. ɹ ɹ ② buildBlockで[Int]の可変長引数を渡せるようにする func someFunction() -> [Int] { let

    v0: [Int] = ArrayBuilder.buildExpression(expression: 1) let v1: [Int] if Bool.random() { let v1_0: [Int] = ArrayBuilder.buildExpression(expression: 2) let v1_1: [Int] = ArrayBuilder.buildExpression(expression: 3) let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildExpression(_ expression: Int) -> [Int] { [expression] } static func buildBlock(_ components: Int...) -> [Int] { components } static func buildOptional(_ component: [Int]?) -> [Int] { ᶄ buildBlockͰ[Int]ͷՄม௕Ҿ਺Λ౉ͤΔΑ͏ʹ͢Δ
  36. ɹ ɹ ② buildBlockで[Int]の可変長引数を渡せるようにする func someFunction() -> [Int] { let

    v0: [Int] = ArrayBuilder.buildExpression(expression: 1) let v1: [Int] if Bool.random() { let v1_0: [Int] = ArrayBuilder.buildExpression(expression: 2) let v1_1: [Int] = ArrayBuilder.buildExpression(expression: 3) let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildExpression(_ expression: Int) -> [Int] { [expression] } static func buildBlock(_ components: [Int]...) -> [Int] { components.flatMap { $0 } } static func buildOptional(_ component: [Int]?) -> [Int] { ᶄ buildBlockͰ[Int]ͷՄม௕Ҿ਺Λ౉ͤΔΑ͏ʹ͢Δ
  37. ɹ ɹ 最終的な実装 func someFunction() -> [Int] { let v0:

    [Int] = ArrayBuilder.buildExpression(expression: 1) let v1: [Int] if Bool.random() { let v1_0: [Int] = ArrayBuilder.buildExpression(expression: 2) let v1_1: [Int] = ArrayBuilder.buildExpression(expression: 3) let v1_block: [Int] = ArrayBuilder.buildBlock(v1_0, v1_1) v1 = ArrayBuilder.buildOptional(v1_block) } else { v1 = ArrayBuilder.buildOptional(nil) } return ArrayBuilder.buildBlock(v0, v1) } @resultBuilder struct ArrayBuilder { static func buildExpression(_ expression: Int) -> [Int] { [expression] } static func buildBlock(_ components: [Int]...) -> [Int] { components.flatMap { $0 } } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } }
  38. ɹ ɹ 最終的な実装 @ArrayBuilder func someFunction() -> [Int] { 1

    if Bool.random() { 2 3 } } @resultBuilder struct ArrayBuilder { static func buildExpression(_ expression: Int) -> [Int] { [expression] } static func buildBlock(_ components: [Int]...) -> [Int] { components.flatMap { $0 } } static func buildOptional(_ component: [Int]?) -> [Int] { component ?? [] } }
  39. ɹ ɹ その他制御構文への対応 for i in (1...3) { i }

    var v0: [Int] = [] for i in (1...3) { // ... v0.append(i_block) } let v0_array = ArrayBuilder.buildArray(v0) if Bool.random() { 1 } else { 2 } if Bool.random() { // ... v0 = ArrayBuilder.buildEither(first: v1_block) } else { // ... v0 = ArrayBuilder.buildEither(second: v2_block) } WWDC2021: Write a DSL in Swift using result builders
  40. ɹ ɹ ϨΠΞ΢τ༻ಠࣗXML ը૾σʔλ ϓϦϯλʔ΁ૹ৴ ม׵ 独自ルールに基づいたXML文字列の生成 <receipt> <image src="http://www.example.com/images/logo.png"

    height="96" /> <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> <spacer /> <text align="center">ϔομʔϝοηʔδ</text> <spacer /> <text truncated="true">ΞΠςϜ໊1</text> <text label="1000ԁ x 2">2000ԁ</text> <text label="ΞΠςϜ໊2" truncated="true">2000ԁ</text> <!-- লུ --> </receipt> ※ 社内の別アプリでの仕組みを、STORES レジ用に移植
  41. ɹ ɹ 独自ルールに基づいたXML文字列の生成 <receipt> <image src="http://www.example.com/images/logo.png" height="96" /> <title>͝ར༻໌ࡉ</title> <ruler

    alpha="0" /> <spacer /> <text align="center">ϔομʔϝοηʔδ</text> <spacer /> <text truncated="true">ΞΠςϜ໊1</text> <text label="1000ԁ x 2">2000ԁ</text> <text label="ΞΠςϜ໊2" truncated="true">2000ԁ</text> <!-- লུ --> </receipt> var xml = "<receipt>" if let src = receiptSetting.logoImageURL?.absoluteString { xml += "<image src=\"\(src)\" height=\“96\”/>” } xml += """ <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> """ for item in items { if item.quantity >= 2 { let label = "(item.price) x \(item.quantity)" xml += """ <text truncated=\"true\"">\(item.name)</text> <text label=\"\(label)\">\(item.totalPrice)</text> “"" } else { xml += "<text label=\”\(item.name)\" truncated=\”true\””>" xml += "\(item.totalPrice)" xml += "</text>" } } // ~ লུ ~ xml += "</receipt>" return xml
  42. ɹ ɹ XMLベタ書きの問題 1.型安全でなく、想定外の文字があると壊れる 2.冗長な記述が多く、レイアウト構造と非対応で見通しが悪い 3.変更があった場合、改修コストが高い 4.指定可能な値の把握がしづらい var xml =

    "<receipt>" if let src = receiptSetting.logoImageURL?.absoluteString { xml += "<image src=\"\(src)\" height=\“96\”/>” } xml += """ <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> """
  43. ɹ ɹ var xml = "<receipt>" if let src =

    receiptSetting.logoImageURL?.absoluteString { xml += "<image src=\"\(src)\" height=\“96\”/>” } xml += """ <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> """ XMLベタ書きの問題 ཁ͸ɺݸʑͷXMLཁૉΛऩूͯ͠ɺจࣈྻΛੜ੒͍ͯ͠Δ͚ͩ → resultBuilderͰͷཁૉͷऩूʹద͍ͯ͠Δ
  44. ɹ ɹ 実装の概要 protocol ReceiptElement { var body: String {

    get } } 各要素を同一視するためのProtocolを定義
  45. ɹ ɹ 実装の概要 protocol ReceiptElement { var body: String {

    get } } struct Text: ReceiptElement { var body: String { “<text>some text</text>" } } struct Image: ReceiptElement { var body: String { “<image src=\”some url\”/>” } } 各要素を同一視するためのProtocolを定義 利用できるXML要素を準拠させる ※ આ໌ͷͨΊ؆ུԽͨ͠ίʔυΛࡌ͍ͤͯ·͢
  46. ɹ ɹ 実装の概要 @resultBuilder struct ReceiptBuilder { static func buildBlock(_

    components: ReceiptElement...) -> [ReceiptElement] { components } static func buildBlock(_ components: [ReceiptElement]...) -> [ReceiptElement] { components.flatMap { $0 } } static func buildExpression(_ expression: ReceiptElement) -> [ReceiptElement] { [expression] } static func buildExpression(_ expression: [ReceiptElement]) -> [ReceiptElement] { expression } static func buildEither(first component: [ReceiptElement]) -> [ReceiptElement] { component } static func buildEither(second component: [ReceiptElement]) -> [ReceiptElement] { component } static func buildOptional(_ component: [ReceiptElement]?) -> [ReceiptElement] { component ?? [] } static func buildArray(_ components: [[ReceiptElement]]) -> [ReceiptElement] { components.flatMap { $0 } } } ReceiptElementをresultBuilderで収集
  47. ɹ ɹ 実装の概要 struct Receipt { let buildElements: () ->

    [ReceiptElement] init(@ReceiptBuilder buildElements: @escaping () -> [ReceiptElement]) { self.buildElements = buildElements } var xml: String { let elements = buildElements() return Tag(.receipt).build(VStack(elements).body) } func image(width: CGFloat) async throws -> UIImage { let builder = ReceiptImageBuilder(width: width) return try await builder.build(xml: xml) } }
  48. ɹ ɹ 実装の概要 struct Receipt { let buildElements: () ->

    [ReceiptElement] init(@ReceiptBuilder buildElements: @escaping () -> [ReceiptElement]) { self.buildElements = buildElements } var xml: String { let elements = buildElements() return Tag(.receipt).build(VStack(elements).body) } func image(width: CGFloat) async throws -> UIImage { let builder = ReceiptImageBuilder(width: width) return try await builder.build(xml: xml) } } ① 1.resultBuilderをイニシャライザで受け取る
  49. ɹ ɹ 実装の概要 struct Receipt { let buildElements: () ->

    [ReceiptElement] init(@ReceiptBuilder buildElements: @escaping () -> [ReceiptElement]) { self.buildElements = buildElements } var xml: String { let elements = buildElements() return Tag(.receipt).build(VStack(elements).body) } func image(width: CGFloat) async throws -> UIImage { let builder = ReceiptImageBuilder(width: width) return try await builder.build(xml: xml) } } ① ② 1.resultBuilderをイニシャライザで受け取る 2.受け取ったresultBuilderでReceiptElementの配列を収集
  50. ɹ ɹ 実装の概要 struct Receipt { let buildElements: () ->

    [ReceiptElement] init(@ReceiptBuilder buildElements: @escaping () -> [ReceiptElement]) { self.buildElements = buildElements } var xml: String { let elements = buildElements() return Tag(.receipt).build(VStack(elements).body) } func image(width: CGFloat) async throws -> UIImage { let builder = ReceiptImageBuilder(width: width) return try await builder.build(xml: xml) } } ① ② ③ 1.resultBuilderをイニシャライザで受け取る 2.受け取ったresultBuilderでReceiptElementの配列を収集 3.収集した要素をXML文字列に変換
  51. ɹ ɹ 実装の概要 1.resultBuilderをイニシャライザで受け取る 2.受け取ったresultBuilderでReceiptElementの配列を収集 3.収集した要素をXML文字列に変換 4.XML文字列を画像に変換 struct Receipt {

    let buildElements: () -> [ReceiptElement] init(@ReceiptBuilder buildElements: @escaping () -> [ReceiptElement]) { self.buildElements = buildElements } var xml: String { let elements = buildElements() return Tag(.receipt).build(VStack(elements).body) } func image(width: CGFloat) async throws -> UIImage { let builder = ReceiptImageBuilder(width: width) return try await builder.build(xml: xml) } } ① ② ③ ④
  52. ɹ ɹ Before: XMLベタ書き var xml = "<receipt>" if let

    src = receiptSetting.logoImageURL?.absoluteString { xml += "<image src=\"\(src)\" height=\“96\”/>” } xml += """ <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> """ for item in items { if item.quantity >= 2 { let label = "(item.price) x \(item.quantity)" xml += """ <text truncated=\"true\"">\(item.name)</text> <text label=\"\(label)\">\(item.totalPrice)</text> “"" } else { xml += "<text label=\”\(item.name)\" truncated=\”true\””>" xml += "\(item.totalPrice)" xml += "</text>" } } // ~ লུ ~ xml += "</receipt>" return xml
  53. ɹ ɹ Before: XMLベタ書き Receipt { if let src =

    receiptSetting.logoImageURL { Image(src: src) .maxHeight(96) } Title(“͝ར༻໌ࡉ") Divider() VStack(spacing: 2) { for item in items { if item.quantity >= 2 { Text(item.name) .truncated() LabelText(label: "\(item.price) x \(item.quantity)", content: item.totalPrice) } else { LabelText(label: item.name, content: item.totalPrice) .truncated() } } } }
  54. ɹ ɹ Before: XMLベタ書き let receiptImage = try await Receipt

    { if let src = receiptSetting.logoImageURL { Image(src: src) .maxHeight(96) } Title(“͝ར༻໌ࡉ") Divider() VStack(spacing: 2) { for item in items { if item.quantity >= 2 { Text(item.name) .truncated() LabelText(label: "\(item.price) x \(item.quantity)", content: item.totalPrice) } else { LabelText(label: item.name, content: item.totalPrice) .truncated() } } } }.image(width: 240)
  55. ɹ ɹ let receiptImage = try await Receipt { if

    let src = receiptSetting.logoImageURL { Image(src: src) .maxHeight(96) } Title(“͝ར༻໌ࡉ") Divider() VStack(spacing: 2) { for item in items { if item.quantity >= 2 { Text(item.name) .truncated() LabelText(label: "\(item.price) x \(item.quantity)", content: item.totalPrice) } else { LabelText(label: item.name, content: item.totalPrice) .truncated() } } } }.image(width: 240) var xml = "<receipt>" if let src = receiptSetting.logoImageURL?.absoluteString { xml += "<image src=\"\(src)\" height=\“96\”/>” } xml += """ <title>͝ར༻໌ࡉ</title> <ruler alpha="0" /> """ for item in items { if item.quantity >= 2 { let label = "(item.price) x \(item.quantity)" xml += """ <text truncated=\"true\"">\(item.name)</text> <text label=\"\(label)\">\(item.totalPrice)</text> “"" } else { xml += "<text label=\”\(item.name)\" truncated=\”true\””>" xml += "\(item.totalPrice)" xml += "</text>" } } // ~ লུ ~ xml += "</receipt>" return xml After: resultBuilder利用