티스토리 뷰
Swift Docs의
- Advanced Operators > Result Builders
를 기반으로 하는 내용입니다.
[1] Result Builder 란?
A result builder is a type you define that adds syntax for creating nested data, like a list or tree, in a natural,
declarative way.
A result builder is a type that builds a nested data structure step by step.
result builder 는 선언적인 방식 (step by step으로 코드를 작성할 수 있는 방식) 으로 nested data를 만들 수 있게 해줍니다.
즉 result builder를 사용해서 DSL(Domain Specific Language)을 만들 수 있는데요,
DSL은 복잡한 계층구조를 표현하는 경우에 유용합니다.
class, structure, enumeration에 @resultBuilder 라는 attribute를 붙여서 result builder 로 사용할 수 있습니다.
(SwiftUI의 @viewBuilder 는 대표적인 result builder의 종류 중 하나 입니다. )
[2] Result Builder 필요성
Result Builder의 필요성에 대해 알아보기 위해 아래 예제를 봅시다.
Single Line을 그리기 위한 몇가지 타입을 정의합니다.
protocol Drawable {
func draw() -> String
}
struct Line: Drawable {
var elements: [Drawable]
func draw() -> String {
return elements.map { $0.draw() }.joined(separator: "")
}
}
struct Text: Drawable {
var content: String
init(_ content: String) { self.content = content }
func draw() -> String { return content }
}
struct Space: Drawable {
func draw() -> String { return " " }
}
struct Stars: Drawable {
var length: Int
func draw() -> String { return String(repeating: "*", count: length) }
}
struct AllCaps: Drawable {
var content: Drawable
func draw() -> String { return content.draw().uppercased() }
}
이런 식으로 원하는 Single Line을 그릴 수 있습니다.
let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
Stars(length: 3),
Text("Hello"),
Space(),
AllCaps(content: Text((name ?? "World") + "!")),
Stars(length: 2),
])
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"
위 코드는 두가지 아쉬운 점이 있습니다.
1. AllCaps 코드가 읽기 어렵습니다.
2. drawing 부분을 build up할 때, switch 나 for loop를 포함해야하는 경우도 있는데, 그럴 수 있는 방법이 없습니다.
이 때, result builder를 사용하면 유용합니다!
[3] Result Builder 만들기 - Step 1
result builder를 정의하려면 type 선언 위에 @resultBuilder attribute 를 작성해주면 됩니다.
DrawingBuilder 라는 타입을 정의해주고 @resultBuilder attribute 를 작성해주겠습니다.
result builder는 이 메소드를 반드시 구현해줘야한다고 컴파일 에러가 뜨네요
buildBlock(_:) method는 하나의 block에 여러줄의 코드를 작성하는 것을 가능하게 해줍니다.
이 메소드의 구현으로는 block 안에 있는 여러 component들을 single result로 combine 해주는 코드를 작성해줍니다.
@resultBuilder
struct DrawingBuilder {
static func buildBlock(_ components: Drawable...) -> Drawable {
return Line(elements: components)
}
}
그리고 draw 라는 function을 만들고 function 파라미터에 @DrawingBuilder attribute를 적용해줍니다.
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
return content()
}
그러면 Swift는 function에 전달된 클로져를 result builder가 해당 클로져에서 생성한 값으로 변환하는 작업을 해줍니다.
정확한 설명을 위해 영어 첨부할게요!
You can apply the @DrawingBuilder attribute to a function’s parameter, which turns a closure passed to the
function into the value that the result builder creates from that closure
Swift transforms that declarative description of a drawing into a series of calls to the methods on DrawingBuilder to build up the value that’s passed as the function argument.
그럼 기존 코드를
let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
Stars(length: 3),
Text("Hello"),
Space(),
AllCaps(content: Text((name ?? "World") + "!")),
Stars(length: 2),
])
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"
더 자연스럽고 선언형인 방식으로 작성할 수 있게 됩니다. (콤마도 작성안해도 됨)
let name: String? = "Ravi Patel"
let manualDrawing = draw {
Stars(length: 3)
Text("Hello")
Space()
AllCaps(content: Text((name ?? "World") + "!"))
Stars(length: 2)
}
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"
[4] Result Builder 만들기 - Step 2
아까 AllCaps 부분이 읽기 어렵다고 했으니까 개선해볼게요!
caps function을 추가하고
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
return AllCaps(content: content())
}
?? opeartor 대신 if, else 문을 쓰도록 바꿨습니다.
let name: String? = "Ravi Patel"
let manualDrawing = draw {
Stars(length: 3)
Text("Hello")
Space()
caps {
if let name = name {
Text(name + "!")
} else {
Text("World!")
}
}
Stars(length: 2)
}
print(manualDrawing.draw())
// Prints "***Hello RAVI PATEL!**"
하지만 if, else 문을 사용하면 아래와 같은 컴파일 에러가 납니다.
block 안에서 if else 문을 사용할 수 있게 해주려면
DrawingBuilder에 두 메소드를 추가해줘야하기 때문입니다.
두 메소드는 단순히 component를 return 해주도록 구현해주면 됩니다.
@resultBuilder
struct DrawingBuilder {
static func buildBlock(_ components: Drawable...) -> Drawable {
return Line(elements: components)
}
static func buildEither(first component: Drawable) -> Drawable {
return component
}
static func buildEither(second component: Drawable) -> Drawable {
return component
}
}
그럼 컴파일 에러 없이 잘 동작하는 것을 볼 수 있습니다.
Swift는 caps(_:)에 대한 call을 아래의 코드처럼 변환합니다.
if-else 블록을 buildEither(first:) 와 buildEither(second:) 에 대한 호출로 변환하는 것을 살펴볼 수 있습니다.
let capsDrawing = caps {
let partialDrawing: Drawable
if let name = name {
let text = Text(name + "!")
partialDrawing = DrawingBuilder.buildEither(first: text)
} else {
let text = Text("World!")
partialDrawing = DrawingBuilder.buildEither(second: text)
}
return partialDrawing
}
[5] Result Builder 만들기 - Step 3
더 나아가 block에서 for loop 도 작성할 수 있도록 DrawingBuilder에 buildArray(_:) 도 추가해볼게요!
extension DrawingBuilder {
static func buildArray(_ components: [Drawable]) -> Drawable {
return Line(elements: components)
}
}
그럼 이제 for loop도 사용할 수 있게 되었습니다 :-)
let stars = draw {
Text("Stars:")
for length in 1...3 {
Space()
Stars(length: length)
}
}
print(stars.draw())
// Prints "Stars: * ** ***"
[ 더 보면 좋을 것 ]
- WWDC 2021 > Write a DSL in Swift using result builders
- SE-0289
✔️ Result-Building Methods 목록이 나와있어요!
https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID633
✔️ SwiftUI 관련 설명도 같이 나와있어요
https://www.hackingwithswift.com/swift/5.4/result-builders
✔️ 실제 사용 예제를 더 볼 수 있어요
https://github.com/carson-katri/awesome-result-builders
'🍏 > Swift' 카테고리의 다른 글
[Swift] @inlinable과 @usableFromInline (1) | 2022.05.17 |
---|---|
[Swift] Operator Overloading / Custom Operator (0) | 2022.02.02 |
[Swift] 10진수 -> 2진수 변환 (positive, negative) (0) | 2022.02.01 |
[Swift] Implicitly Unwrapped Optional 개념과 예제 (0) | 2022.01.27 |
[Swift] Opaque Type vs Protocol Type (0) | 2022.01.23 |
- Total
- Today
- Yesterday
- Flutter Clipboard
- 플러터 싱글톤
- 장고 Custom Management Command
- ipad multitasking
- flutter build mode
- Watch App for iOS App vs Watch App
- Flutter 로딩
- 구글 Geocoding API
- Flutter Text Gradient
- github actions
- Flutter Spacer
- Flutter getter setter
- PencilKit
- flutter dynamic link
- Sketch 누끼
- DRF APIException
- ribs
- drf custom error
- Django Firebase Cloud Messaging
- Python Type Hint
- flutter 앱 출시
- Django Heroku Scheduler
- cocoapod
- Dart Factory
- 플러터 얼럿
- Django FCM
- 장고 URL querystring
- SerializerMethodField
- flutter deep link
- METAL
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |