Первая бета Swift 5.2 только что появилась в Xcode 11.4 beta, и в ней произошли изменения в языке, наряду с сокращением размера кода и используемой памяти, а также появилась новая система диагностики, которая позволит быстрее выявлять ошибки.

Используем KeyPath выражения как функции


Определим такую структуру:

struct User {
    let name: String
    let age: Int
    let bestFriend: String?

    var canVote: Bool {
        age >= 18
    }
}

Создадим несколько экземпляров нашей структуры и положим их в массив:

let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]

Теперь внимание: если вам нужно получить массив имён всех пользователей, вы можете сделать это следующим образом:

let userNames = users.map(\.name)
print(userNames)

Ранее нам нужно было использовать замыкание:

let oldUserNames = users.map { $0.name }

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

let voters = users.filter(\.canVote)

А здесь мы получим всех, у кого есть лучший друг:

let bestFriends = users.compactMap(\.bestFriend)

Значения у определяемых пользователем типов


Создадим структуру Dice со свойствами lowerBound и upperBound, а затем добавим функцию callAsFunction. Таким образом, всякий раз при получении значения dice, мы будем получать случайное значение:

struct Dice {
    var lowerBound: Int
    var upperBound: Int

    func callAsFunction() -> Int {
        (lowerBound...upperBound).randomElement()!
    }
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)

Здесь мы получим случайное число от 1 до 6, и это совершенно идентично прямому вызову callAsFunction(). То же самое мы могли бы сделать так:

let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)

Swift автоматически подбирает верный вызов на основании того, как именно определена callAsFunction(). Например, вы можете добавить несколько параметров, менять возвращаемое значение, и даже, если нужно, помечать метод как mutating.

Здесь мы создадим структуру StepCounter, которая фиксирует число пройденных шагов и сигнализирует, достигло ли количество пройденных шагов 10,000:

struct StepCounter {
    var steps = 0

    mutating func callAsFunction(count: Int) -> Bool {
        steps += count
        print(steps)
        return steps > 10_000
    }
}

var steps = StepCounter()
let targetReached = steps(count: 10)

callAsFunction() поддерживает также throws и rethrows, и вы можете определить несколько callAsFunction() методов, как при обычной перегрузке.

У Subscript можно объявить аргументы по умолчанию


При добавлении subscripts к типу можно использовать аргументы по умолчанию. Например, если у нас есть структура PoliceForce с пользовательским subscript чтобы перечислять офицеров в подразделении, мы можем добавить параметр по умолчанию, чтобы возвращать его в случае чтения массива вне его границ:

struct PoliceForce {
    var officers: [String]

    subscript(index: Int, default default: String = "Unknown") -> String {
        if index >= 0 && index < officers.count {
            return officers[index]
        } else {
            return `default`
        }
    }
}

let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])

Здесь мы получим на выводе “Amy”, а затем “Unknown”, так как у нас нет элемента массива с индексом 5.

Так как мы написали default default, мы можем задавать пользовательское значение, вроде:

print(force[-1, default: "The Vulture"])

Новая и улучшенная диагностика


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

Для примера рассмотрим такой код:

struct ContentView: View {
    @State private var name = 0

    var body: some View {
        VStack {
            Text("What is your name?")
            TextField("Name", text: $name)
                .frame(maxWidth: 300)
        }
    }
}

Здесь мы пытаемся связать view TextField c целым State свойством, что неверно. Swift 5.1 выдаст в таком случае ошибку для модификатора frame() 'Int' is not convertible to 'CGFloat?’, но Swift 5.2 и последующие верно распознают ошибку в биндинге $name: Cannot convert value of type 'Binding' to expected argument type 'Binding’.