struct ContentView: View {
var body: some View {
VStack {
Text(text1)
Text(text2)
}
}
}
some
으로 type-erased 되어있다. (조만간 Any, some 같은 type-eraser의 장단점을 이야기 해볼 것)@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@resultBuilder public struct ViewBuilder {
/// Builds an empty view from a block containing no statements.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view through unmodified.
///
/// An example of a single view written as a child view is
/// `{ Text("Hello") }`.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Provides support for “if” statements in multi-statement closures,
/// producing an optional view that is visible only when the condition
/// evaluates to `true`.
public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View
/// Provides support for "if" statements in multi-statement closures,
/// producing conditional content for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures,
/// producing conditional content for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
Swift 5.4에서 공식적으로 소개 된 result builder는 5.1에서 소개 된 funciton builder가 더 발전된 형태이다. 우리가 VStack, HStack 클로저 안에서 자식 뷰 인스턴스 선언을 차례로 나열할 때, 그 sequence에 따라 tuple을 생성해 그룹핑하고 하나의 자식 view로 돌려주는 wrapper이다. result builder를 사용하면 나만의 stack을 만드는 것이 가능하다.
@resultBuilder
struct StringStack {
static func buildBlock(_ components: String...) -> String {
return components.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
}
사용은 아래와 같이,
@StringStack func fullSentence() -> String {
"this is my beginning"
if true {
"truth will be revealed"
} else {
"it will be never happened"
}
"the end"
}
print(fullSentence())
결과는 아래와같다. 단순히 string을 나열한 것에 불과하지만 joined(separator:\n)
처리를 통해 하나의 통합된 String 객체를 반환받았다. 어떻게 써먹을지에 대해서는 좀더 고민을 해봐야겠다. 일단은 SwiftUI에서 제공하는 기본 View들이 이런 식으로 build pattern을 가지고 있다는 점을 짚어보았다.
this is my beginning
truth will be revealed
the end
자바 생태계에서는 흔하게 접하게 되는 디자인 패턴인 builder pattern는 객체를 생성할 때에 적용할 수있는 디자인 패턴이고 아래와 같이 사용된다.
class MyStruct {
private String requiredMember;
private int optionalMember1;
private boolean optionalMember2;
public String getRequiredMember() {
return requiredMember1;
}
public int getOptionalMember1() {
return this.optionalMember1;
}
public boolean getOptionalMember1() {
return this.optionalMember2;
}
private MyStruct(MyStructBuilder builder) {
this.requiredMember = builder.getRequiredMember();
this.optionalMember1 = builder.getOptionalMember1();
this.optionalMember2 = builder.getOptionalMember2();
}
}
public static class MyStructBuilder {
private String requiredMember;
private int optionalMember1;
private boolean optionalMember2;
public MyStructBuilder(String member) {
this.requiredMember = member;
}
public String getRequiredMember() {
return this.requiredMember;
}
public int getOptionalMember1() {
return this.optionalMember1;
}
public boolean getOptionalMember1() {
return this.optionalMember2;
}
public MyStructBuilder setOptionalMember1(int member) {
this.optionalMember1 = member;
return this;
}
public MyStructBuilder setOptionalMember2(boolean member) {
this.optionalMember2 = member;
return this;
}
public MyStruct build() {
return new MyStruct(this);
}
}
괜히 코드 수만 늘어나는 것처럼 보이는데... 장점은 다음과 같다.
class NoteViewController: UIViewController {
private let note: NSMutableAttributedString // 우리의 state
...
private func append(_ text: Character) {
let string = NSAttributedString(
string: String(text),
attributes: myTextAttribute
)
note.append(string)
}
private func save() {
mydatabase.save(note) {
...
}
}
}
NSMutableAttributedString
으로 선언했다. 해당 클래스는 content를 mutate할 수 있는 메서드를 제공하고 있다. (https://developer.apple.com/documentation/foundation/nsmutableattributedstring)private let noteBuilder = AttributedStringBuilder()
private func appendCharacter(_ text: Character) {
noteBuilder.append(text, attributes: myTextAttribute)
}
private func save() {
let finalnote = noteBuilder.build()
mydatabase.save(finalnote)