View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Code
  • Write a DSL in Swift using result builders

    Some problems are easier to solve by creating a customized programming language, or “domain-specific language.” While creating a DSL traditionally requires writing your own compiler, you can instead use result builders with Swift 5.4 to make your code both easier to read and maintain. We'll take you through best practices for designing a custom language for Swift: Learn about result builders and trailing closure arguments, explore modifier-style methods and why they work well, and discover how you can extend Swift's normal language rules to turn Swift into a DSL.

    To get the most out of this session, it's helpful (though not necessary) to have some experience writing SwiftUI views. You won't need to know anything about parser or compiler implementation.

    Ressources

    • Result Builders - The Swift Programming Language
    • Attributes - The Swift Programming Language
    • Fruta: Building a feature-rich app with SwiftUI
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC21

    • What‘s new in Swift

    WWDC19

    • Modern Swift API Design
  • Rechercher dans cette vidéo…
    • 3:15 - FavoriteSmoothies view

      struct FavoriteSmoothies: View {
          @EnvironmentObject
          private var model: FrutaModel
      
          var body: some View {
              SmoothieList(smoothies: model.favoriteSmoothies)
                  .overlay(
                      Group {
                          if model.favoriteSmoothies.isEmpty {
                              Text("Add some smoothies!")
                                  .foregroundColor(.secondary)
                                  .frame(maxWidth: .infinity,
                                         maxHeight: .infinity) 
                          }
                      }
                  )
                  .navigationTitle("Favorites")
          }
      }
    • 3:38 - FavoriteSmoothies view (hypothetical alternative)

      // Hypothetical code--not actually supported by SwiftUI
      
      struct FavoriteSmoothies: View {
          @EnvironmentObject
          private var model: FrutaModel
      
          var body: some View {
              var list = SmoothieList(smoothies: model.favoriteSmoothies)
              
              let overlay: View
              if model.favoriteSmoothies.isEmpty {
                  var text = Text("Add some smoothies!")
                  text.foregroundColor = .secondary
      
                  var frame = Frame(subview: text)
                  frame.maxWidth = .infinity
                  frame.maxHeight = .infinity
                  overlay = frame
              } else {
                  overlay = EmptyView()
              }
              
              list.addOverlay(overlay)
              list.navigationTitle = "Favorites"
              
              return list
          }
      }
    • 3:59 - FavoriteSmoothies view

      struct FavoriteSmoothies: View {
          @EnvironmentObject
          private var model: FrutaModel
      
          var body: some View {
              SmoothieList(smoothies: model.favoriteSmoothies)
                  .overlay(
                      Group {
                          if model.favoriteSmoothies.isEmpty {
                              Text("Add some smoothies!")
                                  .foregroundColor(.secondary)
                                  .frame(maxWidth: .infinity,
                                         maxHeight: .infinity) 
                          }
                      }
                  )
                  .navigationTitle("Favorites")
          }
      }
    • 6:17 - FavoriteSmoothies view

      struct FavoriteSmoothies: View {
          @EnvironmentObject
          private var model: FrutaModel
      
          var body: some View {
              SmoothieList(smoothies: model.favoriteSmoothies)
                  .overlay(
                      Group {
                          if model.favoriteSmoothies.isEmpty {
                              Text("Add some smoothies!")
                                  .foregroundColor(.secondary)
                                  .frame(maxWidth: .infinity,
                                         maxHeight: .infinity) 
                          }
                      }
                  )
                  .navigationTitle("Favorites")
          }
      }
    • 9:26 - Simple result builder example

      VStack {
          Text("Title").font(.title)
          Text("Contents")
      }
    • 9:36 - Simple result builder example + struct VStack

      VStack {
          Text("Title").font(.title)
          Text("Contents")
      }
      
      
      struct VStack<Content: View>: View {
          …
          init(@ViewBuilder content: () -> Content) {
              self.content = content()
          }
      }
    • 9:40 - Simple result builder example + struct VStack + trailing closure applied

      VStack /* .init(content: */ {
          Text("Title").font(.title)
          Text("Contents")
      } /* ) */
      
      
      struct VStack<Content: View>: View {
          …
          init(@ViewBuilder content: () -> Content) {
              self.content = content()
          }
      }
    • 9:50 - Simple result builder example + struct VStack + trailing closure applied + enum ViewBuilder

      VStack /* .init(content: */ {
          Text("Title").font(.title)
          Text("Contents")
          /* return // TODO: build results using ‘ViewBuilder’ */
      } /* ) */
      
      struct VStack<Content: View>: View {
          …
          init(@ViewBuilder content: () -> Content) {
              self.content = content()
          }
      }
      
      @resultBuilder enum ViewBuilder {
          static func buildBlock(_: View...) -> some View { … }
      }
    • 10:15 - Simple result builder example + struct VStack + trailing closure applied + enum ViewBuilder + result builder applied

      VStack /* .init(content: */ {
          /* let v0 = */ Text("Title").font(.title)
          /* let v1 = */ Text("Contents")
          /* return ViewBuilder.buildBlock(v0, v1) */
      } /* ) */
      
      struct VStack<Content: View>: View {
          …
          init(@ViewBuilder content: () -> Content) {
              self.content = content()
          }
      }
      
      @resultBuilder enum ViewBuilder {
          static func buildBlock(_: View...) -> some View { … }
      }
    • 14:49 - Fruta's smoothie lists, pre-DSL

      // Fruta’s Smoothie lists
      
      extension Smoothie {
          static let berryBlue = Smoothie(
              id: "berry-blue",
              title: "Berry Blue",
              description: "Filling and refreshing, this smoothie will fill you with joy!",
              measuredIngredients: [
                  MeasuredIngredient(.orange, measurement: Measurement(value: 1.5, unit: .cups)),
                  MeasuredIngredient(.blueberry, measurement: Measurement(value: 1, unit: .cups)),
                  MeasuredIngredient(.avocado, measurement: Measurement(value: 0.2, unit: .cups))
              ],
              hasFreeRecipe: true
          )
      
          static let carrotChops = Smoothie(…)
          static let crazyColada = Smoothie(…)
          // Plus 12 more…
      }
      
      extension Smoothie {
          private static let allSmoothies: [Smoothie] = [
              .berryBlue,
              .carrotChops,
              .crazyColada,
              // Plus 12 more…
          ]
      
          static func all(includingPaid: Bool = true) -> [Smoothie] {
              if includingPaid {
                  return allSmoothies
              }
              
              logger.log("Free smoothies only")
              return allSmoothies.filter { $0.hasFreeRecipe }
          }
      }
    • 14:50 - Fruta's smoothie lists, pre-DSL (hypothetical alternative)

      // Fruta’s Smoothie lists (hypothetical alternative)
      
      extension Smoothie {
          static let berryBlue = Smoothie(
              id: "berry-blue",
              title: "Berry Blue",
              description: "Filling and refreshing, this smoothie will fill you with joy!",
              measuredIngredients: [
                  MeasuredIngredient(.orange, measurement: Measurement(value: 1.5, unit: .cups)),
                  MeasuredIngredient(.blueberry, measurement: Measurement(value: 1, unit: .cups)),
                  MeasuredIngredient(.avocado, measurement: Measurement(value: 0.2, unit: .cups))
              ],
              hasFreeRecipe: true
          )
      
          static let carrotChops = Smoothie(…)
          static let crazyColada = Smoothie(…)
          // Plus 12 more…
      }
      
      extension Smoothie {
         static func all(includingPaid: Bool = true) -> [Smoothie] {
             var allSmoothies: [Smoothie] = [
                  .berryBlue,
                  .carrotChops,
              ]
              
              if includingPaid {
                  allSmoothies += [
                      .crazyColada,
                      // Plus more
                  ]
              } else {
                  logger.log("Free smoothies only")
              }
              
              return allSmoothies
          }
      }
    • 14:51 - Fruta's smoothie lists, pre-DSL

      // Fruta’s Smoothie lists
      
      extension Smoothie {
          static let berryBlue = Smoothie(
              id: "berry-blue",
              title: "Berry Blue",
              description: "Filling and refreshing, this smoothie will fill you with joy!",
              measuredIngredients: [
                  MeasuredIngredient(.orange, measurement: Measurement(value: 1.5, unit: .cups)),
                  MeasuredIngredient(.blueberry, measurement: Measurement(value: 1, unit: .cups)),
                  MeasuredIngredient(.avocado, measurement: Measurement(value: 0.2, unit: .cups))
              ],
              hasFreeRecipe: true
          )
      
          static let carrotChops = Smoothie(…)
          static let crazyColada = Smoothie(…)
          // Plus 12 more…
      }
      
      extension Smoothie {
          private static let allSmoothies: [Smoothie] = [
              .berryBlue,
              .carrotChops,
              .crazyColada,
              // Plus 12 more…
          ]
      
          static func all(includingPaid: Bool = true) -> [Smoothie] {
              if includingPaid {
                  return allSmoothies
              }
              
              logger.log("Free smoothies only")
              return allSmoothies.filter { $0.hasFreeRecipe }
          }
      }
    • 18:05 - Near-final DSL design

      // DSL top-level design
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) -> [Smoothie] {
          Smoothie(
              // TODO: Change these parameters
              id: "berry-blue",
              title: "Berry Blue",
              description: "Filling and refreshing, this smoothie will fill you with joy!",
              measuredIngredients: [
                  Ingredient.orange.measured(with: .cups).scaled(by: 1.5),
                  Ingredient.blueberry.measured(with: .cups),
                  Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
              ]
          )
        
          Smoothie(…)
        
          if includingPaid {
              Smoothie(…)
             
              Smoothie(…)
          } else {
              logger.log("Free smoothies only")
          }
      }
    • 19:57 - Possible DSL description/ingredient designs (start)

      // Possible DSL description/ingredient designs
      
      Smoothie(
          id: "berry-blue",
          title: "Berry Blue",
          description: "Filling and refreshing, this smoothie will fill you with joy!",
          measuredIngredients: [
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5),
              Ingredient.blueberry.measured(with: .cups),
              Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          ]
      )
    • 20:11 - Possible DSL description/ingredient designs (modifiers)

      // Possible DSL description/ingredient designs
      
      Smoothie(id: "berry-blue", title: "Berry Blue")
          .description("Filling and refreshing, this smoothie will fill you with joy!")
          .ingredient(Ingredient.orange.measured(with: .cups).scaled(by: 1.5))
          .ingredient(Ingredient.blueberry.measured(with: .cups))
          .ingredient(Ingredient.avocado.measured(with: .cups).scaled(by: 0.2))
    • 20:25 - Possible DSL description/ingredient designs (all marker types)

      // Possible DSL description/ingredient designs
      
      Smoothie {
          ID("berry-blue")
          Title("Berry Blue")
          Description("Filling and refreshing, this smoothie will fill you with joy!")
      
          Recipe(
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5),
              Ingredient.blueberry.measured(with: .cups),
              Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          )
      }
    • 20:36 - Possible DSL description/ingredient designs (some marker types)

      // Possible DSL description/ingredient designs
      
      Smoothie(id: "berry-blue", title: "Berry Blue") {
          Description("Filling and refreshing, this smoothie will fill you with joy!")
      
          Recipe(
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5),
              Ingredient.blueberry.measured(with: .cups),
              Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          )
      }
    • 21:13 - Possible DSL description/ingredient designs (no marker types)

      // Possible DSL description/ingredient designs
      
      Smoothie(id: "berry-blue", title: "Berry Blue") {
          "Filling and refreshing, this smoothie will fill you with joy!"
      
          Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
          Ingredient.blueberry.measured(with: .cups)
          Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
      }
    • 21:43 - Final DSL design

      // DSL top-level design
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) -> [Smoothie] {
          Smoothie(id: "berry-blue", title: "Berry Blue") {
              "Filling and refreshing, this smoothie will fill you with joy!"
      
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
              Ingredient.blueberry.measured(with: .cups)
              Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          }
      
          Smoothie(…) { … }
      
          if includingPaid {
              Smoothie(…) { … }
          } else {
              logger.log("Free smoothies only")
          }
      }
    • 24:05 - Basic SmoothieArrayBuilder

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildBlock(_ components: Smoothie...) -> [Smoothie] {
          return components
        }
      }
    • 24:39 - How ‘buildBlock(…)’ works

      // How ‘buildBlock(…)’ works
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) {
          /* let v0 = */ Smoothie(id: "berry-blue", title: "Berry Blue") { … }
      
          /* let v1 = */ Smoothie(id: "carrot-chops", title: "Carrot Chops") { … }
      
          // …more smoothies…
      
          /* return SmoothieArrayBuilder.buildBlock(v0, v1, …) */
      }
    • 25:03 - Basic SmoothieArrayBuilder

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildBlock(_ components: Smoothie...) -> [Smoothie] {
          return components
        }
      }
    • 25:56 - Smoothie initializer (incomplete)

      extension Smoothie {
        init(id: Smoothie.ID, title: String, /* FIXME */ _ makeIngredients: () -> (String, [MeasuredIngredient])) {
          let (description, ingredients) = makeIngredients()
          self.init(id: id, title: title, description: description, measuredIngredients: ingredients)
        }
      }
    • 27:47 - SmoothieArrayBuilder with simple ‘if’ statements (incorrect)

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: Smoothie...) -> [Smoothie] {
          return components
        }
      }
    • 28:01 - How ‘if’ statements work with ‘buildOptional(_:)’

      // How ‘if’ statements work with ‘buildOptional(_:)’
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) {
          /* let v0 = */ Smoothie(id: "berry-blue", …) { … }
          /* let v1 = */ Smoothie(id: "carrot-chops", …) { … }
      
          /* let v2: [Smoothie] */
          if includingPaid {
              /* let v2_0 = */ Smoothie(id: "crazy-colada", …) { … }
              /* let v2_1 = */ Smoothie(id: "hulking-lemonade", …) { … }
              /* let v2_block = SmoothieArrayBuilder.buildBlock(v2_0, v2_1)
                 v2 = SmoothieArrayBuilder.buildOptional(v2_block) */
          }
          /* else {
              v2 = SmoothieArrayBuilder.buildOptional(nil)
          } */
          
          /* return SmoothieArrayBuilder.buildBlock(v0, v1, v2) */
      }
    • 29:07 - SmoothieArrayBuilder with simple ‘if’ statements (incorrect)

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: Smoothie...) -> [Smoothie] {
          return components
        }
      }
    • 29:28 - Why didn’t our ‘buildOptional(_:)’ work?

      // Why didn’t our ‘buildOptional(_:)’ work?
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) {
          /* let v0 = */ Smoothie(id: "berry-blue", …) { … }
          /* let v1 = */ Smoothie(id: "carrot-chops", …) { … }
      
          /* let v2: [Smoothie] */
          if includingPaid {
              /* let v2_0 = */ Smoothie(id: "crazy-colada", …) { … }
              /* let v2_1 = */ Smoothie(id: "hulking-lemonade", …) { … }
              /* let v2_block = SmoothieArrayBuilder.buildBlock(v2_0, v2_1)
                 v2 = SmoothieArrayBuilder.buildOptional(v2_block) */
          }
          /* else {
              v2 = SmoothieArrayBuilder.buildOptional(nil)
          } */
          
          /* return SmoothieArrayBuilder.buildBlock(v0, v1, v2) */
      }
    • 29:40 - SmoothieArrayBuilder with simple ‘if’ statements (still incorrect)

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: [Smoothie]...) -> [Smoothie] {
          return components.flatMap { $0 }
        }
      }
    • 30:14 - Why didn’t our ‘buildOptional(_:)’ work?

      // Why didn’t our ‘buildOptional(_:)’ work?
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) {
          /* let v0 = */ Smoothie(id: "berry-blue", …) { … }
          /* let v1 = */ Smoothie(id: "carrot-chops", …) { … }
      
          /* let v2: [Smoothie] */
          if includingPaid {
              /* let v2_0 = */ Smoothie(id: "crazy-colada", …) { … }
              /* let v2_1 = */ Smoothie(id: "hulking-lemonade", …) { … }
              /* let v2_block = SmoothieArrayBuilder.buildBlock(v2_0, v2_1)
                 v2 = SmoothieArrayBuilder.buildOptional(v2_block) */
          }
          /* else {
              v2 = SmoothieArrayBuilder.buildOptional(nil)
          } */
          
          /* return SmoothieArrayBuilder.buildBlock(v0, v1, v2) */
      }
    • 31:23 - The ‘buildExpression(_:)’ method

      // The ‘buildExpression(_:)’ method
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) {
          /* let v0 = SmoothieArrayBuilder.buildExpression( */ Smoothie(id: "berry-blue", …) { … } /* ) */
          /* let v1 = SmoothieArrayBuilder.buildExpression( */ Smoothie(id: "carrot-chops", …) { … } /* ) */
      
          /* let v2: [Smoothie] */
          if includingPaid {
              /* let v2_0 = SmoothieArrayBuilder.buildExpression( */ Smoothie(id: "crazy-colada", …) { … } /* ) */
              /* let v2_1 = SmoothieArrayBuilder.buildExpression( */ Smoothie(id: "hulking-lemonade", …) { … } /* ) */
              /* let v2_block = SmoothieArrayBuilder.buildBlock(v2_0, v2_1)
                 v2 = SmoothieArrayBuilder.buildOptional(v2_block) */
          }
          /* else {
              v2 = SmoothieArrayBuilder.buildOptional(nil)
          } */
          
          /* return SmoothieArrayBuilder.buildBlock(v0, v1, v2) */
      }
    • 31:44 - SmoothieArrayBuilder with simple ‘if’ statements (correct)

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: [Smoothie]...) -> [Smoothie] {
          return components.flatMap { $0 }
        }
        
        static func buildExpression(_ expression: Smoothie) -> [Smoothie] {
          return [expression]
        }
      }
    • 32:48 - SmoothieArrayBuilder with ‘if’-‘else’ statements

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildEither(first component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildEither(second component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: [Smoothie]...) -> [Smoothie] {
          return components.flatMap { $0 }
        }
        
        static func buildExpression(_ expression: Smoothie) -> [Smoothie] {
          return [expression]
        }
      }
    • 32:53 - How ‘if’-‘else’ statements work with ‘buildEither(…)’

      // How ‘if’-‘else’ statements work with ‘buildEither(…)’
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) -> [Smoothie] {
          /* let v0: [Smoothie] */
          if includingPaid {
              /* let v0_0 = SmoothieArrayBuilder.buildExpression( */ Smoothie(…) { … } /* ) */
              /* let v0_block = SmoothieArrayBuilder.buildBlock(v0_0)
                 v0 = SmoothieArrayBuilder.buildEither(first: v0_block) */
          }
          else {
              /* let v0_0 = SmoothieArrayBuilder.buildExpression( */ logger.log("Only got free smoothies!") /* ) */
              /* let v0_block = SmoothieArrayBuilder.buildBlock(v0_0)
                 v0 = SmoothieArrayBuilder.buildEither(second: v0_block) */
          }
          
          /* return SmoothieArrayBuilder.buildBlock(v0) */
      }
    • 33:37 - How more complicated statements work with ‘buildEither(…)’

      // How more complicated statements work with ‘buildEither(…)’
      
      var v0: [Smoothie]
      switch userRegion {
      case .americas:
          // ...smoothies omitted...
          /* let v0_block = SmoothieArrayBuilder.buildBlock(...parameters omitted...)
             v0 = SmoothieArrayBuilder.buildEither(first:
                      SmoothieArrayBuilder.buildEither(first: v0_block)) */
        
      case .asiaPacific:
          // ...smoothies omitted...
          /* let v0_block = SmoothieArrayBuilder.buildBlock(…)
             v0 = SmoothieArrayBuilder.buildEither(first:
                      SmoothieArrayBuilder.buildEither(second: v0_block)) */
        
      case .eastAtlantic:
          // ...smoothies omitted...
          /* let v0_block = SmoothieArrayBuilder.buildBlock(…)
             v0 = SmoothieArrayBuilder.buildEither(second: v0_block) */
      }
    • 34:12 - SmoothieArrayBuilder with ‘if’-‘else’ statements

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildEither(first component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildEither(second component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: [Smoothie]...) -> [Smoothie] {
          return components.flatMap { $0 }
        }
        
        static func buildExpression(_ expression: Smoothie) -> [Smoothie] {
          return [expression]
        }
      }
    • 34:54 - How ‘if’-‘else’ statements work with ‘buildEither(…)’

      // How ‘if’-‘else’ statements work with ‘buildEither(…)’
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) -> [Smoothie] {
          /* let v0: [Smoothie] */
          if includingPaid {
              /* let v0_0 = SmoothieArrayBuilder.buildExpression( */ Smoothie(…) { … } /* ) */
              /* let v0_block = SmoothieArrayBuilder.buildBlock(v0_0)
                 v0 = SmoothieArrayBuilder.buildEither(first: v0_block) */
          }
          else {
              /* let v0_0 = SmoothieArrayBuilder.buildExpression( */ logger.log("Only got free smoothies!") /* ) */
              /* let v0_block = SmoothieArrayBuilder.buildBlock(v0_0)
                 v0 = SmoothieArrayBuilder.buildEither(second: v0_block) */
          }
        
          /* return SmoothieArrayBuilder.buildBlock(v0) */
      }
    • 35:07 - SmoothieArrayBuilder with support for ‘Void’ results

      @resultBuilder
      enum SmoothieArrayBuilder {
        static func buildEither(first component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildEither(second component: [Smoothie]) -> [Smoothie] {
          return component
        }
        
        static func buildOptional(_ component: [Smoothie]?) -> [Smoothie] {
          return component ?? []
        }
        
        static func buildBlock(_ components: [Smoothie]...) -> [Smoothie] {
          return components.flatMap { $0 }
        }
        
        static func buildExpression(_ expression: Smoothie) -> [Smoothie] {
          return [expression]
        }
        
        static func buildExpression(_ expression: Void) -> [Smoothie] {
          return []
        }
      }
    • 36:41 - Modifier-style methods on Ingredient and MeasuredIngredient

      extension Ingredient {
        func measured(with unit: UnitVolume) -> MeasuredIngredient {
          MeasuredIngredient(self, measurement: Measurement(value: 1, unit: unit))
        }
      }
      
      extension MeasuredIngredient {
        func scaled(by scale: Double) -> MeasuredIngredient {
          return MeasuredIngredient(ingredient, measurement: measurement * scale)
        }
      }
    • 37:32 - Closures and result builders

      // Closures and result builders
      
      @SmoothieArrayBuilder
      static func all(includingPaid: Bool = true) -> [Smoothie] {
          /* let v0 = SmoothieArrayBuilder.buildExpression( */ Smoothie(…) {
              "Filling and refreshing, this smoothie will fill you with joy!"
              
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
              Ingredient.blueberry.measured(with: .cups)
              Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          } /* ) */
      
          /* let v1 = SmoothieArrayBuilder.buildExpression( */ Smoothie(…) {
              "Packed with vitamin A and C, Carrot Chops is a great way to start your day!"
              
              Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
              Ingredient.carrot.measured(with: .cups).scaled(by: 0.5)
              Ingredient.mango.measured(with: .cups).scaled(by: 0.5)
          } /* ) */
      
          /* return SmoothieArrayBuilder.buildBlock(v0, v1) */
      }
    • 39:22 - Smoothie initializer (final) and SmoothieBuilder (initial)

      extension Smoothie {
        init(id: Smoothie.ID, title: String, @SmoothieBuilder _ makeIngredients: () -> (String, [MeasuredIngredient])) {
          let (description, ingredients) = makeIngredients()
          self.init(id: id, title: title, description: description, measuredIngredients: ingredients)
        }
      }
      
      @resultBuilder
      enum SmoothieBuilder {
        static func buildBlock(_ description: String, components: MeasuredIngredient...) -> (String, [MeasuredIngredient]) {
          return (description, components)
        }
      }
    • 40:38 - Accepting different types

      // Accepting different types
      
      Smoothie(…) /* @SmoothieBuilder */ {
          /* let v0 = */ "Filling and refreshing, this smoothie will fill you with joy!"
          /* let v1 = */ Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
          /* let v2 = */ Ingredient.blueberry.measured(with: .cups)
          /* let v3 = */ Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          
          /* return SmoothieBuilder.buildBlock(v0, v1, v2, v3) */
      }
    • 41:01 - Smoothie initializer (final) and SmoothieBuilder (initial)

      extension Smoothie {
        init(id: Smoothie.ID, title: String, @SmoothieBuilder _ makeIngredients: () -> (String, [MeasuredIngredient])) {
          let (description, ingredients) = makeIngredients()
          self.init(id: id, title: title, description: description, measuredIngredients: ingredients)
        }
      }
      
      @resultBuilder
      enum SmoothieBuilder {
        static func buildBlock(_ description: String, components: MeasuredIngredient...) -> (String, [MeasuredIngredient]) {
          return (description, components)
        }
      }
    • 42:43 - SmoothieBuilder without the string

      // SmoothieBuilder without the string
      
      Smoothie(…) /* @SmoothieBuilder */ {
          // "Filling and refreshing, this smoothie will fill you with joy!"
          /* let v0 = */ Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
          /* let v1 = */ Ingredient.blueberry.measured(with: .cups)
          /* let v2 = */ Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          
          /* return SmoothieBuilder.buildBlock(v0, v1, v2) */
      }
      
      extension SmoothieBuilder {
          static func buildBlock(_ description: String, _ ingredients: ManagedIngredients...) -> (String, [ManagedIngredients]) { … }
      }
    • 43:38 - How Swift improves diagnostics

      // How Swift improves diagnostics
      
      func fn0() throws {}
      func fn1() rethrows {}
      func fn2() {}
      
      func fn3() deinit {}
      
      func fn4() try {}
    • 44:30 - SmoothieBuilder without the string

      // SmoothieBuilder without the string
      
      Smoothie(…) /* @SmoothieBuilder */ {
          // "Filling and refreshing, this smoothie will fill you with joy!"
          /* let v0 = */ Ingredient.orange.measured(with: .cups).scaled(by: 1.5)
          /* let v1 = */ Ingredient.blueberry.measured(with: .cups)
          /* let v2 = */ Ingredient.avocado.measured(with: .cups).scaled(by: 0.2)
          
          /* return SmoothieBuilder.buildBlock(v0, v1, v2) */
      }
      
      extension SmoothieBuilder {
          static func buildBlock(_ description: String, _ ingredients: ManagedIngredients...)
              -> (String, [ManagedIngredients]) { … }
      
          @available(*, unavailable, message: "missing ‘description’ field")
          static func buildBlock(_ ingredients: ManagedIngredients...)
              -> (String, [ManagedIngredients]) { fatalError() }
      }
    • 44:55 - Smoothie initializer (final) and SmoothieBuilder (with error handling)

      extension Smoothie {
        init(id: Smoothie.ID, title: String, @SmoothieBuilder _ makeIngredients: () -> (String, [MeasuredIngredient])) {
          let (description, ingredients) = makeIngredients()
          self.init(id: id, title: title, description: description, measuredIngredients: ingredients)
        }
      }
      
      @resultBuilder
      enum SmoothieBuilder {
        static func buildBlock(_ description: String, components: MeasuredIngredient...) -> (String, [MeasuredIngredient]) {
          return (description, components)
        }
        
        @available(*, unavailable, message: "first statement of SmoothieBuilder must be its description String")
        static func buildBlock(_ components: MeasuredIngredient...) -> (String, [MeasuredIngredient]) {
          fatalError()
        }
      }

Developer Footer

  • Vidéos
  • WWDC21
  • Write a DSL in Swift using result builders
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines