Xcode 9.3 и Swift 4.1, наконец-то, уже не являются бета! Данный релиз содержит долгожданные улучшения стандартной библиотеки и самого языка. Если вы не следите за процессом Swift Evolution, то эта статья для Вас.
В этой статье вы узнаете о наиболее значительных изменениях, внесенных в Swift 4.1.
Эта статья требует наличия Xcode 9.3, поэтому убедитесь, что данная версия Xcode установлена.
Swift 4.1 совместим с исходным кодом Swift 4, поэтому новые функции не нарушают ваш код, если вы уже перенесли свой проект на Swift 4 с помощью Swift Migrator в Xcode.
В следующих разделах вы увидите связанные теги, такие как [SE-0001]. Это номера предложений Swift Evolution. Я добавил ссылку на каждое предложение, чтобы вы могли вникнуть в полную информацию о каждом конкретном изменении. Я рекомендую вам попробовать функции на практике используя Playground, что бы лучше понять, что именно поменяется в вашей работе.
Чтобы начать, запустите Xcode 9.3 и выберите File ? New ? Playground. Выберите iOS в качестве платформы и Blank в качестве шаблона. Назовите и сохраните его по своему усмотрению. Чтобы получить максимальную отдачу от этой статьи, попробуйте на практике каждую функцию в Playground.
Примечание: Если Вы пропустили что же было изменено в Swift 4 и собираетесь наверстать упущенное? Нет проблем! Ознакомьтесь с Swift 4 прочитав What’s New in Swift 4.
В данном релизе имеется ряд улучшений языка, включая условное соответствие, рекурсивные ограничения на связанные типы в протоколах и многое другое.
Условное соответствие допускает соответствие протокола для общих типов, где аргументы типа удовлетворяют определенные условия [SE-0143]. Это мощная функция, которая делает код более гибким. Вы сможете увидеть, как она работает на нескольких примерах.
В Swift 4 вы можете сравнивать массивы, словари и опционалы, если их элементы соответствуют протоколу Equatable. Это работало совершенно нормально для основных сценариев, таких как:
Использование оператора == для проверки равенства в этих примерах было вполне справедливым, поскольку Int является Equatable в Swift 4. Однако сравнение наборов опционалов — обычная ситуация, с которой вы могли столкнуться в Swift 4, поскольку опционалы не соответствуют протоколу Equatable. Swift 4.1 исправляет эту проблему, используя условное соответствие, позволяя сравнивать дополнительные типы с теми типами, которые лежат в основе Equatable:
Int? является Equatable в Swift 4.1, поэтому оператор == работает для [Int?], [String: Int?] и Int??.
Аналогичная проблема была решена при сравнении массивов (например, [[Int]]). В Swift 4 вы можете сравнивать массивы множеств (например, [Set </Int/>]), так как множества соответствуют протоколу Equatable. Swift 4.1 решает это, потому как массивы (и словари), а также их базовые значения являются Equatable.
Как правило, Optional, Array и Dictionary в Swift 4.1 теперь соответствуют протоколам Equatable и Hashable, всякий раз, когда их базовые значения или элементы соответствуют этим протоколам.
Вот пример того, как условная соответствия работают в стандартной библиотеке. Затем вы реализуете это у себя в коде.
Сейчас мы используем условное соответствие, чтобы создать свою собственную группу музыкальных инструментов. Добавьте следующий блок кода в Playground.
Вот, что этот код выполняет шаг за шагом:
Затем объявите группу инструментов:
Вот что вы делаете шаг за шагом:
Затем создайте свои любимые группы инструментов и сравните их:
В этом фрагменте кода вы создаете две Keyboards и Guitar вместе с их соответствующими Bands. Затем вы сравниваете Bands напрямую, благодаря условному соответствию, которое вы определили ранее.
В Swift 4.1 массивы, словари, наборы и дополнительные модули соответствуют протоколу Codable, если их элементы также соответствуют этому протоколу. Добавьте следующий код в Playground:
Вы используете этот код для encode [Student], [String: Student], Set</Student/> и Student?. Этот код хорошо работает в Swift 4.1, так как Student является Codable, что делает эти типы коллекций также соответствующими Codable.
Swift 4.1 позволяет преобразовывать свойства CamelCase в ключи snake_case во время парсинга JSON:
При создании объекта encoder вы устанавливаете свойство keyEncodingStrategy равным .convertToSnakeCase. Посмотрев на консоль, вы должны увидеть:
Вы также можете конвертировать обратно из snake_case в CamelCase во время работы с JSON:
На этот раз для свойства keyDecodingStrategy вы присвоите значение .convertFromSnakeCase.
В Swift 4 требовалось писать шаблонный код, чтобы структуры соответствовали протоколам Equatable и Hashable:
Используя этот код, вы реализуете == (lhs: rhs :) и hashValue для поддержки как Equatable, так и Hashable. Вы можете сравнивать объекты Country, добавлять их в Set’s и даже использовать их в качестве ключей для словаря:
Swift 4.1 добавляет реализации по умолчанию для структур соответствующих Equatable и Hashable, поскольку все ее свойства так же Equalable и Hashable [SE-0185].
Это очень упрощает ваш код, который можно просто переписать как:
Перечисления со связанными значениями также требуют дополнительный код для работы с Equatable и Hashable в Swift 4:
Вы использовали кейсы перечисления для записи реализаций ==(lhs: rhs :) и hashValue. Это позволило вам сравнить сообщения в блогах и использовать их в наборах и словарях:
В отличии от Hashable, размер этого кода является значительно меньшим в Swift 4.1, благодаря реализациям Equatable и Hashable:
Вы просто спасли себя от работы с 20 строками кода шаблона!
Key paths могли использовать индексы, если тип параметра индекса был Hashable в Swift 4. Это позволило им работать с массивами double; например:
Вы используете keyPath для получения текущего номера версии Swift из swiftVersions.
Swift 4.1 добавляет соответствие Hashable ко всем типам нижних индексов в стандартной библиотеке [SE-0188]:
По индекс возвращается первая букву строки. Он работает, поскольку типы индекса String являются Hashable в Swift 4.1.
Swift 4 не поддерживал определение рекурсивных ограничений на связанных типах в протоколах:
В этом примере вы определили тип связанный с SmartPhone, но, возможно, оказалось бы полезным ограничить его до Phone, поскольку все смартфоны являются телефонами. Теперь это возможно в Swift 4.1 [SE-0157]:
Вы используете, where чтобы ограничить как Version, так и SmartPhone, чтобы они были такими же, как у телефона.
Swift 4 поддерживает weak и не unowned свойства протокола:
Вы настроили инструмент в определенном key и pitch. Шаг, возможно, был нулевым, так что вы будете моделировать его, как weak в протоколе Tune.
Но как weak, так и unowned практически бессмысленны, если они определены в самом протоколе, поэтому Swift 4.1 удаляет их, и вы получите предупреждение, используя эти ключевые слова в протоколе [SE-0186]:
Swift 4 использовал IndexDistance для объявления количества элементов в коллекции:
Метод typeOfCollection(_ :) возвращает кортеж, который содержит тип и количество коллекции. Вы можете использовать его для любых коллекций, таких как массивы, словари или наборы; например:
Вы можете улучшить возвращаемый тип функции путем ограничения IndexDistance на Int с предложением where:
Swift 4.1 заменяет IndexDistance на Int в стандартной библиотеке, поэтому в этом случае вам не нужно предложение where [SE-0191]:
Добавление свойств к структурам public может привести к исходным изменениям в Swift 4. В этой статьи убедитесь, что Project Navigator виден в Xcode, перейдя в меню View\Navigators\Show Project Navigator. Затем щелкните правой кнопкой мыши «Sources» и выберите «New File» в меню. Переименуйте файл DiceKit.swift. Замените его содержимое следующим блоком кода:
Инициализатор структуры гарантирует, что обе игральные кости имеют действительные значения между 1 и 6. Вернитесь в Playground и добавьте в конце этот код:
Вот что вы сделали с этим кодом:
В Swift 4.1 перекрестные целевые инициализаторы (cross-target initializers) должны вызывать значение по умолчанию. Измените расширение Dice на:
Это изменение приводит к тому, что структуры ведут себя как классы: инициализаторы кросс-модулей должны быть инициализаторами удобства в Swift 4.1 [SE-0189].
В Swift 4.1 вы больше не сможете обманывать игру в кости!
Swift 4.1 добавляет некоторые необходимые функции платформы и сборки для тестирования кода:
В Swift 4 вы тестировали модуль если он доступен на определенной платформе, определяя саму операционную систему: например:
UIKit доступен на iOS и tvOS, поэтому вы импортировали его, если тест был пройден успешно. Swift 4.1 упрощает этот процесс, позволяя вам проверить сам модуль:
В Swift 4.1 вы используете #if canImport (UIKit), чтобы подтвердить, что определенная структура доступна для импорта [SE-0075].
При написании кода в Swift 4 самым известным способом проверки выполнения кода на симуляторе или физическом устройстве, была проверка архитектуры и операционной системы:
Была ли архитектура вашего процессора построена на базе Intel, а ваша операционная система — iOS, tvOS или watchOS, вы тестировали в симуляторе. В ином случае вы тестировали устройство.
Этот тест был очень громоздким, и он также в полной мере не описывал тип ошибок. Swift 4.1 делает этот тест более простым; просто используйте targetEnvironment (simulator) [ SE-0190 ] следующим образом:
В Swift 4.1 есть еще несколько обновлений, которые стоит знать:
В Swift 4 было довольно распространено использование flatMap(_ :) для фильтрации значений nil из последовательности:
К сожалению, flatMap(_ :) был перегружен различными способами и, в этом конкретном сценарии, присвоение flatMap(_ :) не очень описывало предпринятые действия.
По этим причинам Swift 4.1 представляет переименование flatMap(_ :) в compactMap(_ :), чтобы сделать его смысл более понятным и уникальным [SE-0187]:
Swift 4 использовал временные небезопасные изменяемые указатели для создания и изменения небезопасных изменяемых указателей буфера:
Swift 4.1 позволяет работать с небезопасными изменяемыми указателями буфера напрямую, используя тот же подход, что и с небезопасными изменяемыми указателями [SE-0184]:
Swift 4 позволил вам настроить описания типов в Playground Xcode:
Вы реализовали CustomPlaygroundQuickLookable для Tutorial и возвращения краткого описания. Тип описания в customPlaygroundQuickLook был ограничен случаями PlaygroundQuickLook. такого каламбура в Swift 4.1 уже нету:
На этот раз вы реализуете CustomPlaygroundDisplayConvertible. Тип описания — Any, поэтому вы можете вернуть все, что угодно из playgroundDescription. Это упрощает ваш код и делает его более гибким [SE-0198].
Swift 4.1 улучшает некоторые функции Swift 4 в подготовке к более серьезным изменениям, которые появятся в Swift 5 в этом году. К ним относятся стабильность ABI, улучшенные дженерики и строки, новые модели владения памятью и параллелизма и многое другое.
Если вы чувствуете себя авантюристом, перейдите и посмотрите на стандартную библиотеку Swift или на официальный сайт Swift CHANGELOG, где вы можете прочитать больше информации обо всех изменениях в этой версии.
Если вам интересно, какие изменения будут в Swift 5, мы также рекомендуем вам ознакомиться с предложениями Swift Evolution, где вы можете увидеть новые функции, изменения и дополнения.
В этой статье вы узнаете о наиболее значительных изменениях, внесенных в Swift 4.1.
Эта статья требует наличия Xcode 9.3, поэтому убедитесь, что данная версия Xcode установлена.
Первые Шаги
Swift 4.1 совместим с исходным кодом Swift 4, поэтому новые функции не нарушают ваш код, если вы уже перенесли свой проект на Swift 4 с помощью Swift Migrator в Xcode.
В следующих разделах вы увидите связанные теги, такие как [SE-0001]. Это номера предложений Swift Evolution. Я добавил ссылку на каждое предложение, чтобы вы могли вникнуть в полную информацию о каждом конкретном изменении. Я рекомендую вам попробовать функции на практике используя Playground, что бы лучше понять, что именно поменяется в вашей работе.
Чтобы начать, запустите Xcode 9.3 и выберите File ? New ? Playground. Выберите iOS в качестве платформы и Blank в качестве шаблона. Назовите и сохраните его по своему усмотрению. Чтобы получить максимальную отдачу от этой статьи, попробуйте на практике каждую функцию в Playground.
Примечание: Если Вы пропустили что же было изменено в Swift 4 и собираетесь наверстать упущенное? Нет проблем! Ознакомьтесь с Swift 4 прочитав What’s New in Swift 4.
Улучшения языка
В данном релизе имеется ряд улучшений языка, включая условное соответствие, рекурсивные ограничения на связанные типы в протоколах и многое другое.
Условное соответствие
Условное соответствие допускает соответствие протокола для общих типов, где аргументы типа удовлетворяют определенные условия [SE-0143]. Это мощная функция, которая делает код более гибким. Вы сможете увидеть, как она работает на нескольких примерах.
Условное соответствие в стандартной библиотеке
В Swift 4 вы можете сравнивать массивы, словари и опционалы, если их элементы соответствуют протоколу Equatable. Это работало совершенно нормально для основных сценариев, таких как:
// Arrays of Int
let firstArray = [1, 2, 3]
let secondArray = [1, 2, 3]
let sameArray = firstArray == secondArray
// Dictionaries with Int values
let firstDictionary = ["Cosmin": 10, "George": 9]
let secondDictionary = ["Cosmin": 10, "George": 9]
let sameDictionary = firstDictionary == secondDictionary
// Comparing Int?
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional
Использование оператора == для проверки равенства в этих примерах было вполне справедливым, поскольку Int является Equatable в Swift 4. Однако сравнение наборов опционалов — обычная ситуация, с которой вы могли столкнуться в Swift 4, поскольку опционалы не соответствуют протоколу Equatable. Swift 4.1 исправляет эту проблему, используя условное соответствие, позволяя сравнивать дополнительные типы с теми типами, которые лежат в основе Equatable:
// Array of Int?
let firstArray = [1, nil, 2, nil, 3, nil]
let secondArray = [1, nil, 2, nil, 3, nil]
let sameArray = firstArray == secondArray
// Dictionary with Int? values
let firstDictionary = ["Cosmin": 10, "George": nil]
let secondDictionary = ["Cosmin": 10, "George": nil]
let sameDictionary = firstDictionary == secondDictionary
// Comparing Int?? (Optional of Optional)
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional
Int? является Equatable в Swift 4.1, поэтому оператор == работает для [Int?], [String: Int?] и Int??.
Аналогичная проблема была решена при сравнении массивов (например, [[Int]]). В Swift 4 вы можете сравнивать массивы множеств (например, [Set </Int/>]), так как множества соответствуют протоколу Equatable. Swift 4.1 решает это, потому как массивы (и словари), а также их базовые значения являются Equatable.
let firstArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]
let secondArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]
// Will work in Swift 4 and Swift 4.1
// since Set<Int> is Equatable
firstArrayOfSets == secondArrayOfSets
let firstArrayOfArrays = [[1, 2, 3], [3, 4, 5]]
let secondArrayOfArrays = [[1, 2, 3], [3, 4, 5]]
// Caused an error in Swift 4, but works in Swift 4.1
// since Arrays are Equatable in Swift 4.1
firstArrayOfArrays == secondArrayOfArrays
Как правило, Optional, Array и Dictionary в Swift 4.1 теперь соответствуют протоколам Equatable и Hashable, всякий раз, когда их базовые значения или элементы соответствуют этим протоколам.
Вот пример того, как условная соответствия работают в стандартной библиотеке. Затем вы реализуете это у себя в коде.
Условное соответствие на практике
Сейчас мы используем условное соответствие, чтобы создать свою собственную группу музыкальных инструментов. Добавьте следующий блок кода в Playground.
// 1
class LeadInstrument: Equatable {
let brand: String
init(brand: String) {
self.brand = brand
}
func tune() -> String {
return "Standard tuning."
}
static func ==(lhs: LeadInstrument, rhs: LeadInstrument) -> Bool {
return lhs.brand == rhs.brand
}
}
// 2
class Keyboard: LeadInstrument {
override func tune() -> String {
return "Keyboard standard tuning."
}
}
// 3
class Guitar: LeadInstrument {
override func tune() -> String {
return "Guitar standard tuning."
}
}
Вот, что этот код выполняет шаг за шагом:
- Класс LeadInstrument соответствует протоколу Equatable. Он имеет определенный brand и метод tune(), который вы будете использовать для настройки инструмента.
- Вы переопределяете метод tune() в классе Keyboard, чтобы вернуть стандартные настройки для объекта.
- Вы делаете то же самое для класса Guitar.
Затем объявите группу инструментов:
// 1
class Band<LeadInstrument> {
let name: String
let lead: LeadInstrument
init(name: String, lead: LeadInstrument) {
self.name = name
self.lead = lead
}
}
// 2
extension Band: Equatable where LeadInstrument: Equatable {
static func ==(lhs: Band<LeadInstrument>, rhs: Band<LeadInstrument>) -> Bool {
return lhs.name == rhs.name && lhs.lead == rhs.lead
}
}
Вот что вы делаете шаг за шагом:
- Вы создаете класс Band типа — LeadInstrument. Каждая группа имеет уникальное name(имя) и lead instrument(основной инструмент).
- Вы используете, where чтобы Band соответствовал протоколу Equatable, так же как и LeadInstrument выполняет определенные действия. Здесь и проявляется условное соответствие — вы можете присвоить соответствие протоколу Equatable для дженерика LeadInstruments.
Затем создайте свои любимые группы инструментов и сравните их:
// 1
let rolandKeyboard = Keyboard(brand: "Roland")
let rolandBand = Band(name: "Keys", lead: rolandKeyboard)
let yamahaKeyboard = Keyboard(brand: "Yamaha")
let yamahaBand = Band(name: "Keys", lead: yamahaKeyboard)
let sameBand = rolandBand == yamahaBand
// 2
let fenderGuitar = Guitar(brand: "Fender")
let fenderBand = Band(name: "Strings", lead: fenderGuitar)
let ibanezGuitar = Guitar(brand: "Ibanez")
let ibanezBand = Band(name: "Strings", lead: ibanezGuitar)
let sameBands = fenderBand == ibanezBand
В этом фрагменте кода вы создаете две Keyboards и Guitar вместе с их соответствующими Bands. Затем вы сравниваете Bands напрямую, благодаря условному соответствию, которое вы определили ранее.
Условное соответствие в разборе JSON
В Swift 4.1 массивы, словари, наборы и дополнительные модули соответствуют протоколу Codable, если их элементы также соответствуют этому протоколу. Добавьте следующий код в Playground:
struct Student: Codable, Hashable {
let firstName: String
let averageGrade: Int
}
let cosmin = Student(firstName: "Cosmin", averageGrade: 10)
let george = Student(firstName: "George", averageGrade: 9)
let encoder = JSONEncoder()
// Encode an Array of students
let students = [cosmin, george]
do {
try encoder.encode(students)
} catch {
print("Failed encoding students array: \(error)")
}
// Encode a Dictionary with student values
let studentsDictionary = ["Cosmin": cosmin, "George": george]
do {
try encoder.encode(studentsDictionary)
} catch {
print("Failed encoding students dictionary: \(error)")
}
// Encode a Set of students
let studentsSet: Set = [cosmin, george]
do {
try encoder.encode(studentsSet)
} catch {
print("Failed encoding students set: \(error)")
}
// Encode an Optional Student
let optionalStudent: Student? = cosmin
do {
try encoder.encode(optionalStudent)
} catch {
print("Failed encoding optional student: \(error)")
}
Вы используете этот код для encode [Student], [String: Student], Set</Student/> и Student?. Этот код хорошо работает в Swift 4.1, так как Student является Codable, что делает эти типы коллекций также соответствующими Codable.
Преобразование между CamelCase и Snake_Case при работе с JSON
Swift 4.1 позволяет преобразовывать свойства CamelCase в ключи snake_case во время парсинга JSON:
var jsonData = Data()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
do {
jsonData = try encoder.encode(students)
} catch {
print(error)
}
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
При создании объекта encoder вы устанавливаете свойство keyEncodingStrategy равным .convertToSnakeCase. Посмотрев на консоль, вы должны увидеть:
[
{
"first_name" : "Cosmin",
"average_grade" : 10
},
{
"first_name" : "George",
"average_grade" : 9
}
]
Вы также можете конвертировать обратно из snake_case в CamelCase во время работы с JSON:
var studentsInfo: [Student] = []
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
studentsInfo = try decoder.decode([Student].self, from: jsonData)
} catch {
print(error)
}
for studentInfo in studentsInfo {
print("\(studentInfo.firstName) \(studentInfo.averageGrade)")
}
На этот раз для свойства keyDecodingStrategy вы присвоите значение .convertFromSnakeCase.
Соответствие и совместимость протоколов Equatable и Hashable
В Swift 4 требовалось писать шаблонный код, чтобы структуры соответствовали протоколам Equatable и Hashable:
struct Country: Hashable {
let name: String
let capital: String
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
var hashValue: Int {
return name.hashValue ^ capital.hashValue &* 16777619
}
}
Используя этот код, вы реализуете == (lhs: rhs :) и hashValue для поддержки как Equatable, так и Hashable. Вы можете сравнивать объекты Country, добавлять их в Set’s и даже использовать их в качестве ключей для словаря:
let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let sameCountry = france == germany
let countries: Set = [france, germany]
let greetings = [france: "Bonjour", germany: "Guten Tag"]
Swift 4.1 добавляет реализации по умолчанию для структур соответствующих Equatable и Hashable, поскольку все ее свойства так же Equalable и Hashable [SE-0185].
Это очень упрощает ваш код, который можно просто переписать как:
struct Country: Hashable {
let name: String
let capital: String
}
Перечисления со связанными значениями также требуют дополнительный код для работы с Equatable и Hashable в Swift 4:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
static func ==(lhs: BlogPost, rhs: BlogPost) -> Bool {
switch (lhs, rhs) {
case let (.tutorial(lhsTutorialTitle, lhsTutorialAuthor), .tutorial(rhsTutorialTitle,
rhsTutorialAuthor)):
return lhsTutorialTitle == rhsTutorialTitle && lhsTutorialAuthor == rhsTutorialAuthor
case let (.article(lhsArticleTitle, lhsArticleAuthor), .article(rhsArticleTitle, rhsArticleAuthor)):
return lhsArticleTitle == rhsArticleTitle && lhsArticleAuthor == rhsArticleAuthor
default:
return false
}
}
var hashValue: Int {
switch self {
case let .tutorial(tutorialTitle, tutorialAuthor):
return tutorialTitle.hashValue ^ tutorialAuthor.hashValue &* 16777619
case let .article(articleTitle, articleAuthor):
return articleTitle.hashValue ^ articleAuthor.hashValue &* 16777619
}
}
}
Вы использовали кейсы перечисления для записи реализаций ==(lhs: rhs :) и hashValue. Это позволило вам сравнить сообщения в блогах и использовать их в наборах и словарях:
let swift3Article = BlogPost.article("What's New in Swift 3.1?", "Cosmin Pupaza")
let swift4Article = BlogPost.article("What's New in Swift 4.1?", "Cosmin Pupaza")
let sameArticle = swift3Article == swift4Article
let swiftArticlesSet: Set = [swift3Article, swift4Article]
let swiftArticlesDictionary = [swift3Article: "Swift 3.1 article", swift4Article: "Swift 4.1 article"]
В отличии от Hashable, размер этого кода является значительно меньшим в Swift 4.1, благодаря реализациям Equatable и Hashable:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
}
Вы просто спасли себя от работы с 20 строками кода шаблона!
Hashable Index Types
Key paths могли использовать индексы, если тип параметра индекса был Hashable в Swift 4. Это позволило им работать с массивами double; например:
let swiftVersions = [3, 3.1, 4, 4.1]
let path = \[Double].[swiftVersions.count - 1]
let latestVersion = swiftVersions[keyPath: path]
Вы используете keyPath для получения текущего номера версии Swift из swiftVersions.
Swift 4.1 добавляет соответствие Hashable ко всем типам нижних индексов в стандартной библиотеке [SE-0188]:
let me = "Cosmin"
let newPath = \String.[me.startIndex]
let myInitial = me[keyPath: newPath]
По индекс возвращается первая букву строки. Он работает, поскольку типы индекса String являются Hashable в Swift 4.1.
Рекурсивные ограничения на связанных типах в протоколах
Swift 4 не поддерживал определение рекурсивных ограничений на связанных типах в протоколах:
protocol Phone {
associatedtype Version
associatedtype SmartPhone
}
class IPhone: Phone {
typealias Version = String
typealias SmartPhone = IPhone
}
В этом примере вы определили тип связанный с SmartPhone, но, возможно, оказалось бы полезным ограничить его до Phone, поскольку все смартфоны являются телефонами. Теперь это возможно в Swift 4.1 [SE-0157]:
protocol Phone {
associatedtype Version
associatedtype SmartPhone: Phone where SmartPhone.Version == Version, SmartPhone.SmartPhone == SmartPhone
}
Вы используете, where чтобы ограничить как Version, так и SmartPhone, чтобы они были такими же, как у телефона.
Слабые и не занятые ссылки в протоколах
Swift 4 поддерживает weak и не unowned свойства протокола:
class Key {}
class Pitch {}
protocol Tune {
unowned var key: Key { get set }
weak var pitch: Pitch? { get set }
}
class Instrument: Tune {
var key: Key
var pitch: Pitch?
init(key: Key, pitch: Pitch?) {
self.key = key
self.pitch = pitch
}
}
Вы настроили инструмент в определенном key и pitch. Шаг, возможно, был нулевым, так что вы будете моделировать его, как weak в протоколе Tune.
Но как weak, так и unowned практически бессмысленны, если они определены в самом протоколе, поэтому Swift 4.1 удаляет их, и вы получите предупреждение, используя эти ключевые слова в протоколе [SE-0186]:
protocol Tune {
var key: Key { get set }
var pitch: Pitch? { get set }
}
Index Distances in Collections
Swift 4 использовал IndexDistance для объявления количества элементов в коллекции:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, C.IndexDistance) {
let collectionType: String
switch collection.count {
case 0...100:
collectionType = "small"
case 101...1000:
collectionType = "medium"
case 1001...:
collectionType = "big"
default:
collectionType = "unknown"
}
return (collectionType, collection.count)
}
Метод typeOfCollection(_ :) возвращает кортеж, который содержит тип и количество коллекции. Вы можете использовать его для любых коллекций, таких как массивы, словари или наборы; например:
typeOfCollection(1...800) // ("medium", 800)
typeOfCollection(greetings) // ("small", 2)
Вы можете улучшить возвращаемый тип функции путем ограничения IndexDistance на Int с предложением where:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) where C.IndexDistance == Int {
// тот же код, что и в приведенном выше примере
}
Swift 4.1 заменяет IndexDistance на Int в стандартной библиотеке, поэтому в этом случае вам не нужно предложение where [SE-0191]:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) {
// тот же код, что и в приведенном выше примере
}
Инициализаторы структуры в модулях
Добавление свойств к структурам public может привести к исходным изменениям в Swift 4. В этой статьи убедитесь, что Project Navigator виден в Xcode, перейдя в меню View\Navigators\Show Project Navigator. Затем щелкните правой кнопкой мыши «Sources» и выберите «New File» в меню. Переименуйте файл DiceKit.swift. Замените его содержимое следующим блоком кода:
public struct Dice {
public let firstDie: Int
public let secondDie: Int
public init(_ value: Int) {
let finalValue: Int
switch value {
case ..<1:
finalValue = 1
case 6...:
finalValue = 6
default:
finalValue = value
}
firstDie = finalValue
secondDie = 7 - finalValue
}
}
Инициализатор структуры гарантирует, что обе игральные кости имеют действительные значения между 1 и 6. Вернитесь в Playground и добавьте в конце этот код:
// 1
let dice = Dice(0)
dice.firstDie
dice.secondDie
// 2
extension Dice {
init(_ firstValue: Int, _ secondValue: Int) {
firstDie = firstValue
secondDie = secondValue
}
}
// 3
let newDice = Dice(0, 7)
newDice.firstDie
newDice.secondDie
Вот что вы сделали с этим кодом:
- Вы создали валидную пару игральных кубиков.
- Вы добавили Dice посредством другого инициализатора, который имеет прямой доступ к его свойствам .
- Вы определили недействительную пару игральных кубиков с новым инициализатором структуры.
В Swift 4.1 перекрестные целевые инициализаторы (cross-target initializers) должны вызывать значение по умолчанию. Измените расширение Dice на:
extension Dice {
init(_ firstValue: Int, _ secondValue: Int) {
self.init(abs(firstValue - secondValue))
}
}
Это изменение приводит к тому, что структуры ведут себя как классы: инициализаторы кросс-модулей должны быть инициализаторами удобства в Swift 4.1 [SE-0189].
В Swift 4.1 вы больше не сможете обманывать игру в кости!
Настройки платформы и обновления конфигурации
Swift 4.1 добавляет некоторые необходимые функции платформы и сборки для тестирования кода:
Сборка импортов/Build Imports
В Swift 4 вы тестировали модуль если он доступен на определенной платформе, определяя саму операционную систему: например:
#if os(iOS) || os(tvOS)
import UIKit
print("UIKit is available on this platform.")
#else
print("UIKit is not available on this platform.")
#endif
UIKit доступен на iOS и tvOS, поэтому вы импортировали его, если тест был пройден успешно. Swift 4.1 упрощает этот процесс, позволяя вам проверить сам модуль:
#if canImport(UIKit)
print("UIKit is available if this is printed!")
#endif
В Swift 4.1 вы используете #if canImport (UIKit), чтобы подтвердить, что определенная структура доступна для импорта [SE-0075].
Target Environments
При написании кода в Swift 4 самым известным способом проверки выполнения кода на симуляторе или физическом устройстве, была проверка архитектуры и операционной системы:
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS) || os(watchOS))
print("Testing in the simulator.")
#else
print("Testing on the device.")
#endif
Была ли архитектура вашего процессора построена на базе Intel, а ваша операционная система — iOS, tvOS или watchOS, вы тестировали в симуляторе. В ином случае вы тестировали устройство.
Этот тест был очень громоздким, и он также в полной мере не описывал тип ошибок. Swift 4.1 делает этот тест более простым; просто используйте targetEnvironment (simulator) [ SE-0190 ] следующим образом:
#if targetEnvironment(simulator)
print("Testing in the simulator.")
#endif
Miscellaneous Bits and Pieces
В Swift 4.1 есть еще несколько обновлений, которые стоит знать:
Compacting Sequences/Последовательности сжатия
В Swift 4 было довольно распространено использование flatMap(_ :) для фильтрации значений nil из последовательности:
let pets = ["Sclip", nil, "Nori", nil]
let petNames = pets.flatMap { $0 } // ["Sclip", "Nori"]
К сожалению, flatMap(_ :) был перегружен различными способами и, в этом конкретном сценарии, присвоение flatMap(_ :) не очень описывало предпринятые действия.
По этим причинам Swift 4.1 представляет переименование flatMap(_ :) в compactMap(_ :), чтобы сделать его смысл более понятным и уникальным [SE-0187]:
let petNames = pets.compactMap {$ 0}
Unsafe Pointers/Небезопасные указатели
Swift 4 использовал временные небезопасные изменяемые указатели для создания и изменения небезопасных изменяемых указателей буфера:
let buffer = UnsafeMutableBufferPointer<Int>(start: UnsafeMutablePointer<Int>.allocate(capacity: 10),
count: 10)
let mutableBuffer = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(mutating: buffer.baseAddress),
count: buffer.count)
Swift 4.1 позволяет работать с небезопасными изменяемыми указателями буфера напрямую, используя тот же подход, что и с небезопасными изменяемыми указателями [SE-0184]:
Новые возможности Playground
Swift 4 позволил вам настроить описания типов в Playground Xcode:
class Tutorial {}
extension Tutorial: CustomPlaygroundQuickLookable {
var customPlaygroundQuickLook: PlaygroundQuickLook {
return .text("raywenderlich.com tutorial")
}
}
let tutorial = Tutorial()
Вы реализовали CustomPlaygroundQuickLookable для Tutorial и возвращения краткого описания. Тип описания в customPlaygroundQuickLook был ограничен случаями PlaygroundQuickLook. такого каламбура в Swift 4.1 уже нету:
extension Tutorial: CustomPlaygroundDisplayConvertible {
var playgroundDescription: Any {
return "raywenderlich.com tutorial"
}
}
На этот раз вы реализуете CustomPlaygroundDisplayConvertible. Тип описания — Any, поэтому вы можете вернуть все, что угодно из playgroundDescription. Это упрощает ваш код и делает его более гибким [SE-0198].
Что далее?
Swift 4.1 улучшает некоторые функции Swift 4 в подготовке к более серьезным изменениям, которые появятся в Swift 5 в этом году. К ним относятся стабильность ABI, улучшенные дженерики и строки, новые модели владения памятью и параллелизма и многое другое.
Если вы чувствуете себя авантюристом, перейдите и посмотрите на стандартную библиотеку Swift или на официальный сайт Swift CHANGELOG, где вы можете прочитать больше информации обо всех изменениях в этой версии.
Если вам интересно, какие изменения будут в Swift 5, мы также рекомендуем вам ознакомиться с предложениями Swift Evolution, где вы можете увидеть новые функции, изменения и дополнения.