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