Конструктор результатов можно рассматривать как встроенный предметно-ориентированный язык (DSL) для сбора деталей, которые объединяются в конечный результат. Конструкторы результатов в Swift позволяют создавать результат, используя "блоки сборки", расположенные в ряд друг за другом.

Примеры.

Вот функция, которая возвращает одну строку:

func makeSentence1() -> String {
    "Why settle for a Duke when you can have a Prince?"
}

print(makeSentence1())

Это отлично работает, но что, если бы у нас было несколько строк, которые мы хотели объединить? Мы могли бы захотеть предоставить их все по отдельности и попросить Swift разобраться с этим, однако такой код не сработает:

// This is invalid Swift, and will not compile.
 func makeSentence2() -> String {
     "Why settle for a Duke"
     "when you can have"
     "a Prince?"
 }

Сам по себе этот код не будет работать, потому что Swift больше не понимает, что мы имеем в виду. Однако мы могли бы создать конструктор результатов, который понимает, как преобразовать несколько строк в одну строку, используя любое преобразование, которое мы хотим, например, так:

@resultBuilder
struct SimpleStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}

Атрибут @resultBuilder сообщает Swift, что следующий тип следует рассматривать как средство построения результатов. Ранее такое поведение было достигнуто с помощью @_functionBuilder, который имел символ подчеркивания, чтобы показать, что он не предназначен для общего использования.

Каждый конструктор результатов должен предоставлять по крайней мере один статический метод, называемый buildBlock(), который должен принимать какие-либо данные и преобразовывать их. В приведенном выше примере берется ноль или более строк, они объединяются и отправляются обратно в виде одной строки.

Конечным результатом является то, что наша структура SimpleStringBuilderстановится компоновщиком результатов, что означает, что мы можем использовать @SimpleStringBuilder везде, где нам нужны его возможности объединения строк.

Мы можем использовать SimpleStringBuilder.buildBlock() напрямую:

let joined = SimpleStringBuilder.buildBlock(
    "Why settle for a Duke",
    "when you can have",
    "a Prince?"
)

print(joined)

Однако, поскольку мы использовали аннотацию @resultBuilder с нашей структурой SimpleStringBuilder, мы также можем применить ее к функциям:

@SimpleStringBuilder func makeSentence3() -> String {
    "Why settle for a Duke"
    "when you can have"
    "a Prince?"
}

print(makeSentence3())

Обратите внимание, что нам больше не нужны запятые в конце каждой строки – @resultBuilder автоматически преобразует каждый оператор в makeSentence() в отдельную строку с помощью SimpleStringBuilder.

На практике конструкторы результатов способны на значительно большее, что достигается добавлением большего количества методов к вашему типу конструктора. Например, мы могли бы добавить поддержку if/else в наш простой конструктор строк, добавив два дополнительных метода, которые описывают, как мы хотим преобразовать данные. В нашем коде мы вообще не хотим преобразовывать наши строки, поэтому можем сразу вернуть их обратно:

@resultBuilder
struct ConditionalStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }

    static func buildEither(first component: String) -> String {
        return component
    }

    static func buildEither(second component: String) -> String {
        return component
    }
}

Теперь наши функции могут использовать условия:

@ConditionalStringBuilder func makeSentence4() -> String {
    "Why settle for a Duke"
    "when you can have"

    if Bool.random() {
        "a Prince?"
    } else {
        "a King?"
    }
}

print(makeSentence4())

Аналогично, мы можем добавить поддержку циклов, добавив метод buildArray() к нашему типу builder:

@resultBuilder
struct ComplexStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }

    static func buildEither(first component: String) -> String {
        return component
    }

    static func buildEither(second component: String) -> String {
        return component
    }

    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: "\n")
    }
}

Теперь мы можем использовать циклы:

@ComplexStringBuilder func countDown() -> String {
    for i in (0...10).reversed() {
        "\(i)…"
    }

    "Lift off!"
}

print(countDown())

Swift 5.4 расширяет систему построения результатов для поддержки размещения атрибутов в сохраненных свойствах, что автоматически настраивает неявный инициализатор по элементам для структур для применения построителя результатов.

Это особенно полезно для пользовательских представлений SwiftUI, в которых используются построители результатов, такие как этот:

import SwiftUI

struct CustomVStack<Content: View>: View {
    @ViewBuilder let content: Content

    var body: some View {
        VStack {
            // custom functionality here
            content
        }
    }
}

Если вы хотите увидеть более продвинутые, реальные примеры построения результатов в действии, вам следует ознакомиться с https://github.com/carson-katri/awesome-result-builders

Комментарии (0)