Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 클린 코드
- Observable
- SWIFT
- ios
- UICollectionView
- Xcode
- swiftUI
- HIG
- swift documentation
- collectionview
- 애니메이션
- tableView
- 리팩토링
- 리펙터링
- combine
- Human interface guide
- rxswift
- 리펙토링
- clean architecture
- uiscrollview
- ribs
- RxCocoa
- MVVM
- uitableview
- Protocol
- map
- UITextView
- Clean Code
- Refactoring
- 스위프트
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] ResultBuilder, @resultBuilder (빌더, return 없이 사용 방법) 본문
iOS 응용 (swift)
[iOS - swift] ResultBuilder, @resultBuilder (빌더, return 없이 사용 방법)
jake-kim 2021. 12. 31. 23:05ResultBuilder
- Swift 5.4에 도입
- return 부분의 옵션을 설정하여 return 키워드를 없이 사용할수 있거나, 콤마를 사용하지 않고 배열을 만들 수 있는 등의 기능 사용 가능
- ex) 여러 표현식을 단일 값으로 결합하여 결과를 빌드하는 경우
resultBuilder를 사용하지 않은 경우 - 콤마 존재 o | resultBuilder를 사용한 경우 - 콤마 존재 x |
func getPersonMock() -> [Person] { [ Person(name: "jake", age: 20), Person(name: "kim", age: 22), Person(name: "paul", age: 32) ] } |
@PersonBuilder func getPerson() -> [Person] { Person(name: "jake", age: 20) Person(name: "kim", age: 22) Person(name: "paul", age: 32) } |
- ex) return 키워드를 사용하지 않아도 동작
private func getPerson() -> Person { builder { // <- return 키워드 x if num % 2 == 0 { Person(name: "jake", age: 2) // <- return 키워드 x } else { Person(name: "jake", age: 1) // <- return 키워드 x } } } |
private func getPersonNoResultBuilder() -> Person { build { // 컴파일 오류 ! if num % 2 == 0 { Person(name: "jake", age: 2) // 경고: 사용되지 않는 변수 } else { Person(name: "jake", age: 1) // 경고: 사용되지 않는 변수 } } } |
ResultBuilder 사용 예시
ex) Person이라는 구조체에서 name값 뒤에 "🍎"을 붙인 name으로 리네임하는 프로그램
struct Person {
var name: String
let age: Int
}
- ResultBuilder를 사용하지 않은 경우
// 기능 func build(_ components: Person...) -> [Person] { components.map { Person(name: $0.name + "🍎", age: $0.age) } } // 사용 func getPersonMock() -> [Person] { [ Person(name: "jake", age: 20), Person(name: "kim", age: 22), Person(name: "paul", age: 32) ] } let sample = getPersonMock() print(sample) // [ExDynamicCell.Person(name: "jake", age: 20), ExDynamicCell.Person(name: "kim", age: 22), ExDynamicCell.Person(name: "paul", age: 32)]
- ResultBuilder를 사용한 경우 콤마를 사용하지 않아도 가능
- @resultBuilder 키워드를 이용하여 Builder 컴포넌트 정의
- 키워드를 이용하면, static func buildBlock(_ components:) 메소드를 강제 재정의가 필요
@resultBuilder struct PersonBuilder { static func buildBlock(_ components: Person...) -> [Person] { components.map { Person(name: $0.name + "🍎", age: $0.age) } } }
- @resultBuilder키워드가 등록된 PersonBuilder를 사용 -> 여러 표현식을 단일 값으로 결합하여 결과를 빌드
(콤마를 사용하지 않아도 결합)
@PersonBuilder func getPerson() -> [Person] { Person(name: "jake", age: 20) Person(name: "kim", age: 22) Person(name: "paul", age: 32) }
응용 - builder 만들어서 사용하기
> return 키워드를 사용하지 않아도 builder를 통해 리턴임을 암시하여 컴파일 에러가 뜨지 않으므로 간결한 코드 형성
> if문, else문 안에서도 return 키워드를 사용하지 않아도 리턴임을 암시
private func getPerson() -> Person {
builder { // <- return 키워드 없어도 가능
if num % 2 == 0 {
Person(name: "jake", age: 2) // <- return 키워드 없어도 가능
} else {
Person(name: "jake", age: 1) // <- return 키워드 없어도 가능
}
}
}
- builder의 3가지 메소드를 정의
- buildBlock(_:): result를 컴바인하기 위한 필수로 정의가 필요한 메소드
- buildEither(first:): if-else와 switch에서 result가 가능하도록하는 메소드 (buildEither(second:)도 하나의 세트처럼 정의)
- buildEither(second:)
// 출처: https://github.com/SwiftDocOrg/CommonMark/blob/main/Sources/CommonMark/Result%20Builders/ContainerOfBlocksBuilder.swift
#if swift(>=5.4)
@resultBuilder
public struct ContainerOfBlocksBuilder {
/// Required by every result builder to build combined results from
/// statement blocks.
public static func buildBlock(_ components: [Block & Node]...) -> [Block & Node] {
return components.flatMap { $0 }
}
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: Block & Node) -> [Block & Node] {
return [expression]
}
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: [Block & Node]) -> [Block & Node] {
return expression
}
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: Section) -> [Block & Node] {
return expression.children
}
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: String?) -> [Block & Node] {
guard let expression = expression, !expression.isEmpty else { return [] }
let document = try? Document(expression)
// Unlink the children from the document node to prevent dangling pointers to the parent.
let children = document?.removeChildren() ?? []
return children
}
/// Enables support for `if` statements that do not have an `else`.
public static func buildOptional(_ component: [Block & Node]?) -> [Block & Node] {
return component ?? []
}
/// With buildEither(second:), enables support for 'if-else' and 'switch'
/// statements by folding conditional results into a single result.
public static func buildEither(first component: [Block & Node]) -> [Block & Node] {
return component
}
/// With buildEither(first:), enables support for 'if-else' and 'switch'
/// statements by folding conditional results into a single result.
public static func buildEither(second component: [Block & Node]) -> [Block & Node] {
return component
}
/// Enables support for 'for..in' loops by combining the
/// results of all iterations into a single result.
public static func buildArray(_ components: [[Block & Node]]) -> [Block & Node] {
return components.flatMap { $0 }
}
/// If declared, this will be called on the partial result of an 'if
/// #available' block to allow the result builder to erase type
/// information.
public static func buildLimitedAvailability(_ component: [Block & Node]) -> [Block & Node] {
return component
}
}
#endif
- resultBuilder를 사용하지 않고 builder 함수를 만들어서 사용하면 compile error 발생
// resultBuilder를 사용하지 않은 빌더 함수 func build<T>(_ closure: () -> T) -> T { closure() }
- resuiltBuilder를 사용
- @resuiltBuilder로 builder 함수 정의: 제네릭을 이용하여 런타임 시점에, 타입을 선언하고 선언한 타입을 클로저의 리턴값으로 반환
@resultBuilder enum Builder<T> { static func buildBlock(_ component: T) -> T { component } static func buildEither(first component: T) -> T { component } static func buildEither(second component: T) -> T { component } } func builder<T>(@Builder<T> _ closure: () -> T) -> T { closure() }
- 사용: return 키워드 없이 builder { } 블록을 통해 바로 사용 가능
private func getPerson() -> Person { builder { if num % 2 == 0 { Person(name: "jake", age: 2) } else { Person(name: "jake", age: 1) } } }
- @resuiltBuilder로 builder 함수 정의: 제네릭을 이용하여 런타임 시점에, 타입을 선언하고 선언한 타입을 클로저의 리턴값으로 반환
* 전체 소스 코드
import UIKit
struct Person {
var name: String
let age: Int
}
@resultBuilder
enum Builder<T> {
static func buildBlock(_ component: T) -> T { component }
static func buildEither(first component: T) -> T { component }
static func buildEither(second component: T) -> T { component }
}
func builder<T>(@Builder<T> _ closure: () -> T) -> T { closure() }
class ViewController: UIViewController {
let num = 1
override func viewDidLoad() {
super.viewDidLoad()
// 빌더를 사용하지 않은 경우
// let num = 1
// let value: Person
// if num % 2 == 0 {
// value = Person(name: "jake", age: 1)
// } else {
// value = Person(name: "paul", age: 2)
// }
// print(value)
// 빌더를 사용한 경우
let num = 1
let value: Person = builder {
if num % 2 == 0 {
Person(name: "jake", age: 2)
} else {
Person(name: "jake", age: 1)
}
}
print(value)
}
private func getPerson() -> Person {
builder {
if num % 2 == 0 {
Person(name: "jake", age: 2)
} else {
Person(name: "jake", age: 1)
}
}
}
// 컴파일 에러!
// private func getPersonNoResultBuilder() -> Person {
// build {
// if num % 2 == 0 {
// Person(name: "jake", age: 2)
// } else {
// Person(name: "jake", age: 1)
// }
// }
// }
}
// result builder를 사용하지 않은 빌더 함수
func build<T>(_ closure: () -> T) -> T { closure() }
* 참고
https://medium.com/bootcampers/swift-result-builder-building-declarative-stackview-320f588fa47b
https://www.raywenderlich.com/books/swift-apprentice/v7.0/chapters/20-result-builders
'iOS 응용 (swift)' 카테고리의 다른 글
Comments