Туториалы
25 февраля 2021
Туториалы
25 февраля 2021
Что нового в Swift 5.4?

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

Совет: вы также можете загрузить этот Playground в Xcode, если хотите самостоятельно поиграть с кодом.

Улучшенный синтаксис неявных членов

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

Swift всегда имел возможность использовать синтаксис неявных членов для простых выражений, например, если вы хотите раскрасить какой-то текст в SwiftUI, вы можете использовать .red вместо Color.red:

struct ContentView1: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red)
    }
}

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

struct ContentView2: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(Color.red.opacity(0.5))
    }
}

Начиная со Swift 5.4 компилятор может понимать несколько связанных членов, а это означает, что тип Color может быть выведен:

struct ContentView3: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red.opacity(0.5))
    }
}

Параметры с переменным числом аргументов в функциях

SE-0284 предоставляет возможность использовать в функциях, индексах и инициализаторах несколько параметров с переменным числом аргументов, если все параметры, следующие за параметром с переменным числом аргументов, имеют ярлык. До Swift 5.4 в этой ситуации мог быть только один вариативный параметр. Итак, с этим улучшением мы могли бы написать функцию, которая принимает переменный параметр, хранящий время, когда голы были забиты во время футбольного матча, плюс второй вариативный параметр, хранящий имена игроков отличившихся голом:

func summarizeGoals(times: Int..., players: String...) {
    let joinedNames = ListFormatter.localizedString(byJoining: players)
    let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))

    print("\(times.count) goals where scored by \(joinedNames) at the follow minutes: \(joinedTimes)")
}

Чтобы вызвать эту функцию, предоставьте оба набора значений как вариативные параметры, убедившись, что все параметры после первых вариативных помечены:

summarizeGoals(times: 18, 33, 55, 90, players: "Dani", "Jamie", "Roy")

Строители результатов

Строители функций неофициально появились в Swift 5.1, но в ходе подготовки к Swift 5.4 они формально прошли процесс предложения Swift Evolution как SE-0289 для обсуждения и улучшения. В рамках этого процесса они были переименованы в строители результатов, чтобы лучше отражать их истинное предназначение, и даже обрели некоторую новую функциональность
.
Во-первых, самая важная часть: строители результатов позволяют нам шаг за шагом создавать новую величину, передавая последовательность по нашему выбору. Они обеспечивают работу больших частей системы создания SwiftUI’s view, поэтому, когда у нас есть VStack с множеством view внутри, Swift группирует их во внутренний тип TupleView, чтобы их можно было сохранить как один дочерний элемент VStack - создается последовательность view в едином view.
Строители результатов заслуживают отдельной подробной статьи, но я по крайней мере хочу дать вам несколько небольших примеров кода, чтобы вы могли увидеть их в действии.

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

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

print(makeSentence1())

Это отлично работает, но что, если бы мы хотели объединить несколько строк? Так же, как SwiftUI, мы могли бы предоставить их все по отдельности, и 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 сообщает SwiftUI, что следующий тип следует рассматривать как строитель результатов. Ранее такое поведение достигалось с помощью @_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 в наш SimpleStringBuilder, добавив два дополнительных метода, которые описывают, как мы хотим преобразовать данные. В нашем коде мы вообще не хотим преобразовывать наши строки, поэтому мы можем отправить их обратно:

@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() к нашему типу строителя:

@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")
    }
}

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

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

    "Lift off!"
}

print(countDown())

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

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

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

struct CustomVStack: View {
    @ViewBuilder let content: Content

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

Если вы хотите увидеть более продвинутые, реальные примеры строителей результатов в действии, вам следует заглянуть в репозиторий Awesome Function Builders на GitHub.

Локальные функции теперь поддерживают перегрузку

SR-10069 запросил возможность перегрузки функций в локальных контекстах, это означает на практике, что вложенные функции теперь могут быть перегружены: Swift выбирает, какую из них запускать, в зависимости от используемых типов.
Например, если бы мы хотели написать код для приготовления печений, то он бы выглядел примерно так:

struct Butter { }
struct Flour { }
struct Sugar { }

func makeCookies() {
    func add(item: Butter) {
        print("Adding butter…")
    }

    func add(item: Flour) {
        print("Adding flour…")
    }

    func add(item: Sugar) {
        print("Adding sugar…")
    }

    add(item: Butter())
    add(item: Flour())
    add(item: Sugar())
}

До Swift 5.4 три метода add() могли быть перегружены, только если они не были вложены в makeCookies(), но начиная со Swift 5.4 и в этом случае поддерживается перегрузка функций.

Обёртки свойств теперь поддерживаются для локальных переменных

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

Например, мы могли бы создать обертку свойства, которая гарантирует, что его значение никогда не опускается ниже нуля:

@propertyWrapper struct NonNegative {
    var value: T

    var wrappedValue: T {
        get { value }

        set {
            if newValue < 0 {
                value = 0
            } else {
                value = newValue
            }
        }
    }

    init(wrappedValue: T) {
        if wrappedValue < 0 {
            self.value = 0
        } else {
            self.value = wrappedValue
        }
    }
}

А начиная с Swift 5.4 и далее мы можем использовать эту обертку свойства внутри обычной функции, а не просто присоединяя ее к свойству. Например, мы могли бы написать игру, в которой наш игрок может набирать или терять очки, но его оценка никогда не должна опускаться ниже 0:

func playGame() {
    @NonNegative var score = 0

    // player was correct
    score += 4

    // player was correct again
    score += 8

    // player got one wrong
    score -= 15

    // player got another one wrong
    score -= 16

    print(score)
}

Пакеты теперь могут объявлять исполняемые target-ы

SE-0294 добавляет новую опцию для target-а в приложениях, использующих Swift Package Manager, что позволяет нам явно объявлять исполняемый target.
Это особенно важно для людей, которые хотят использовать SE-0281 (используя @main для обозначения точки входа в вашу программу), потому что это не очень хорошо работает со Swift Package Manager - он всегда будет искать файл main.swift.
С этим изменением теперь мы можем удалить main.swift и использовать вместо него @main.
Примечание: вы должны указать // swift-tools-version:5.4 в файле Package.swift, чтобы получить эту новую функциональность.

Попробуй сам

Swift 5.4 доступен через Xcode 12.5, который перешел в бета-версию 1 февраля 2021 года. Если вы еще не обновились до macOS Big Sur, вы не сможете установить Xcode 12.5, поэтому вместо этого вам следует загрузить Swift 5.4 toolchain с https://swift.org/download/ - вы можете установить это для Xcode 12.4 и более ранних версий.

Каких возможностей Swift 5.4 вы ждете больше всего?

Оригинал статьи


Оцените статью
0
0
0
0
0

Чтобы добавить комментарий, авторизуйтесь
Войти
Акулов Иван Борисович
Пишет и переводит статьи для SwiftBook