Swift 5.0 стал доступен с выходом Xcode 10.2, но работа над следующей версией продолжается и уже есть известия о том, чего в ней можно ждать.
Ключевая особенность Swift 5.1 — модульная стабильность (module stability), которая позволяет нам использовать сторонние библиотеки, не волнуясь о том, при помощи какой версии компилятора Swift они были созданы. Похоже на ABI stability, которую мы получили в Swift 5.0, но есть небольшая разница: ABI stability разрешает различия в версиях Swift на этапе выполнения, а module stability — на этапе компиляции.
Кроме этого важного новшества мы получим несколько важных улучшений в Swift, и в этой статье мы пройдемся по ним с примерами, чтобы можно было увидеть их в деле.
Универсальный Self
SE-0068 расширяет использование Self, так что он ссылается на содержащий его тип внутри классов, структур и перечислений. Обычно это полезно для динамических типов, когда необходимо определить точный тип чего-либо во время выполнения.
В качестве примера рассмотрим следующий код:
class NetworkManager {
class var maximumActiveRequests: Int {
return 4
}
func printDebugData() {
print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
}
}
Здесь мы определяем статическое свойство maximumActiveRequests внутри класса NetworkManager и добавляем метод printDebugData() чтобы распечатать это свойство. Здесь все отлично, но только до тех пор, пока мы не решим наследоваться от NetworkManager:
class ThrottledNetworkManager: NetworkManager {
override class var maximumActiveRequests: Int {
return 1
}
}
В этом наследнике мы меняем свойство maximumActiveRequests, так что теперь оно становится равным единице, но если мы вызовем printDebugData(), то он выведет значение из родительского класса:
let manager = ThrottledNetworkManager()
manager.printDebugData()
Здесь мы должны получить 1 вместо 4, и вот тут на помощь приходит SE-0068: мы можем использовать Self (с прописной 'S'), чтобы сослаться на текущий тип. Так что теперь мы можем переписать метод printDebugData() родительского класса вот так:
class ImprovedNetworkManager {
class var maximumActiveRequests: Int {
return 4
}
func printDebugData() {
print("Maximum network requests: \(Self.maximumActiveRequests).")
}
}
То есть, Self работает таким же образом, как он работал в протоколах в ранних версиях Swift.
Предупреждения в случае двусмысленности варианта none
Optionals в Swift реализованы в качестве перечисления с двумя вариантами: some и none. Это может привести к путанице, если мы создадим своё собственное перечисление, у которого есть вариант none, и обернём его в optional. Например:
enum BorderStyle {
case none
case solid(thickness: Int)
}
При использовании non-optional всё чисто:
let border1: BorderStyle = .none
print(border1)
Это выведет “none”. Но если мы используем optional для этого перечисления, то мы столкнёмся с проблемой:
let border2: BorderStyle? = .none
print(border2)
Здесь будет напечатано nil, так как Swift полагает, что .none означает, что optional пуста, хотя на самом деле это optional со значением BorderStyle.none.
В Swift 5.1 в случае такой двусмысленности будет выведено предупреждение:
“Assuming you mean 'Optional.none'; did you mean 'BorderStyle.none' instead?”
Таким образом разработчик будет проинформирован о том, что с его кодом может быть не всё гладко.
Сопоставление optional и non-optional перечислений
Swift достаточно сообразителен чтобы разобраться в конструкции switch/case при сочетании optional/non-optional текстовых и целых значений, но только не в случае перечислений.
Теперь в Swift 5.1 мы можем использовать switch/case для поиска соответствия перечислений-optional и non-optional варианта:
enum BuildStatus {
case starting
case inProgress
case complete
}
let status: BuildStatus? = .inProgress
switch status {
case .inProgress:
print("Build is starting…")
case .complete:
print("Build is complete!")
default:
print("Some other build status")
}
Swift в состоянии сопоставить optional перечисление с non-optional вариантами, и здесь будет выведено “Build is starting…”
Сравнение упорядоченных коллекций
SE-0240 представило возможность вычислять различия между упорядоченными коллекциями, а также применять к коллекциям полученный результат сравнения. Это может представлять интерес для разработчиков, у которых сложные коллекции в tableview, и им нужно добавлять или удалять много элементов, используя анимацию.
Основной принцип простой — Swift 5.1 предоставляет новый метод difference(from:), который определяет отличия между двумя упорядоченными коллекциями — какие элементы добавить, а какие — удалить. Это применимо к любым упорядоченным коллекциям, которые содержат элементы, отвечающий протоколу Equatable.
Чтобы продемонстрировать это, мы создадим два массива значений, вычислим отличия одного от другого, а затем пройдемся по списку отличий и применим их, чтобы сделать две коллекции одинаковыми.
Замечание: так как Swift теперь распространяется в составе операционных систем Apple, новые средства языка должны использоваться с проверкой #available, чтобы быть уверенным, что код запускается на ОС, которая поддерживает требуемый функционал. Для функционала, запускаемого на неизвестных, неанонсированных ОС, которые могут быть выпущены в будущем, используется специальный номер версии “9999”, который означает: «Мы еще не знаем правильный номер версии».
var scores1 = [100, 91, 95, 98, 100]
let scores2 = [100, 98, 95, 91, 100]
if #available(iOS 9999, *) {
let diff = scores2.difference(from: scores1)
for change in diff {
switch change {
case .remove(let offset, _, _):
scores1.remove(at: offset)
case .insert(let offset, let element, _):
scores1.insert(element, at: offset)
}
}
print(scores1)
}
Для более продвинутой анимации мы можем использовать третий параметр в полученном списке различий: associatedWith. Таким образом, вместо .insert(let offset, let element, _) мы можем написать .insert(let offset, let element, let associatedWith). Это даёт нам возможность одновременно отслеживать пары изменений: передвижение элемента в коллекции на две позиции вниз это удаление элемента с последующим добавлением, а associatedWith «связывает» эти два изменения вместе и позволяет вам считать это передвижением.
Вместо применения различий вручную, одно за одним, вы можете применить их одним махом, используя новый метод applying():
if #available(iOS 9999, *) {
let diff = scores2.difference(from: scores1)
let result = scores1.applying(diff) ?? []
}
Создание неинициализированных массивов
SE-0245 представило новый инициалайзер для массивов, который не заполняет его дефолтными значениями. Это было доступно и раньше как private API, что означало, что Xcode не подсказывал это в code completion, но вы могли это использовать если вам это было нужно и вы понимали риск того, что этого функционала может не быть в дальнейшем.
Для использования инициалайзера задайте размер массива, затем передайте замыкание, которое заполнит массив значениями. Замыкание получит небезопасный указатель на mutable буфер, а также второй параметр, в котором вы обозначите, сколько значений вы на самом деле используете.
Например мы можем создать массив из 10 случайных целых чисел вот так:
let randomNumbers = Array<Int>(_unsafeUninitializedCapacity: 10) { buffer, initializedCount in
for x in 0..<10 {
buffer[x] = Int.random(in: 0...10)
}
initializedCount = 10
}
Тут есть несколько правил:
- вам не нужно использовать весь объём, который вы запросили, но вы не можете превысить его. То есть, если вы задали размер массива 10, то вы можете установить initializedCount в пределах от 0 до 10, но не 11.
- если вы не инициализировали используемые элементы в массиве, например, вы установили initializedCount равным 5, но не предоставили реальных значений элементам с 0 по 4, то они вероятнее всего получат рандомные значения. Как вы понимаете, это плохой вариант.
- Если вы не установите initializedCount, то он будет равным 0 и все данные, которые вы назначили, будут потеряны.
Да, мы вполне могли бы переписать код с использованием map():
let randomNumbers2 = (0...9).map { _ in Int.random(in: 0...10) }
Это очевидно более читабельно, но не так эффективно: мы создаём диапазон, затем новый пустой массив, назначаем ему размер, и «пробегаемся» по всему диапазону, применяя замыкание к каждому элементу.
Заключение
Swift 5.1 все еще находится в стадии разработки, и хотя окончательное ветвление для самого Swift прошло, все еще видны изменения от некоторых других связанных проектов.
Итак, самое важное изменение — это module stability, и известно, что команда разработчиков напряженно над этим работает. Они не называют точной даты выхода, хотя они говорили, что у Swift 5.1 значительно меньший срок разработки по сравнению с Swift 5.0, который потребовал незаурядной концентрации сил и внимания. Можно предположить выход к WWDC19, но очевидно, что это не тот случай, когда нужно спешить к определённой дате.
Ещё одно замечание, которое заслуживает внимания. Два изменения в этом списке («Предупреждения в случае двусмысленности варианта none» и «Сопоставление optional и non-optional перечислений») стали не результатом эволюции Swift, а были распознаны как баги и скорректированы.
Комментарии (8)
Yoooriii
05.04.2019 00:27swift — кросплатформенный, более того Foundation framework также кросплатформенный и свободный. В качестве примера, на свифте можно довольно просто запилить свой сервер. Все автоматом чекаутится из гитхаба, компилится и запускается с пол пинка на хостинге, в том числе и бесплатном. Для разработки макбук не нужен, можно все клепать на коленке в любимом редакторе, хотя в Xcode это делать слегка удобней.
inv2004
05.04.2019 21:09ну я как-то начал смотреть о том как начать его использовать на windows, и показалось что это не очень «из коробки»: swiftforwindows.github.io
ManWithBear
08.04.2019 11:37Смотрите в официальном репе:
github.com/apple/swift/blob/d1db4ea6f2f1c3f054abf8563e283432c7d5fa95/docs/Windows.md
inv2004
Swift интересен, но останавливает его окукливание в пределах apple-платформ. Ведутся ли какие-то действия по упрощению его использования на Win/Lin?
infund Автор
Смотря что подразумевается под использованием. Swift — это просто язык, а сейчас без тех или иных фреймворков, на голом языке (!) и
светодиодом не помигаешьhelloworld не напишешь. Есть проект Swift for Windows, но я не слежу за его состоянием. Если мне нужно что-то быстро проверить или уточнить, а под руками только Windows, я использую что-то вроде этого.ManWithBear
Unix платформы поддерживаются с самого начала. Над Windows тоже работают.
Можете попробовать сами:
github.com/apple/swift/blob/d1db4ea6f2f1c3f054abf8563e283432c7d5fa95/docs/Windows.md