
Конструктор результатов можно рассматривать как встроенный предметно-ориентированный язык (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
