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 Pupăză") let swift4Article = BlogPost.article("What's New in Swift 4.1?", "Cosmin Pupăză") 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, где вы можете увидеть новые функции, изменения и дополнения.
