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

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

たまねぎ
September 12, 2022

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

たまねぎ

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利用