Swift 4.0 против Swift 3.0 - различия и новые функции

Опубликовано: 2021-10-05

День, когда Apple выпустит новую версию хорошо известного языка Swift, наконец-то поразил нас. Чего нам от этого ожидать? В трепете ожидания мы решили представить крошечный обзор свежих обновлений, которые будут присутствовать в версии Swift 4 .

3 Краеугольные камни Swift.

Как фантастический язык для написания кода, Swift имеет свои преимущества и, как утверждается, «пережил» язык Objective-C.

Приглашаем вас прочитать об основных различиях между Swift и Objective-C.

Swift быстр , безопасен по типу и очень выразителен . Его можно было использовать для написания программного обеспечения на телефонах и планшетах, настольных компьютерах и серверах - очевидно, на всем, что запускает код. Он приглашает вас поиграть с ним - с помощью приложения Apple для обучения программированию Swift Playgrounds или с помощью Playgrounds в Xcode вы можете сразу увидеть результаты своей работы, не нужно ломать голову над разработкой и запуском приложения на первое место. С каждой новой версией присадки она становится лучше и быстрее, как и в случае с версией Swift 4.
На старт внимание?

Еще одна замечательная функция, которой обладает Xcode 9 для Swift 4 - вам не нужно сильно беспокоиться о предстоящей миграции, и вы поймете почему, читая эту статью.

Говоря об этом, давайте кратко рассмотрим, какие бонусы и новые функции Swift 4 принесут нам этой осенью.

Начиная

Сам по себе язык не очень полезен без удобной IDE, которой  в мире разработчиков является Xcode. Вы можете загрузить последнюю версию Xcode 9 из Mac App Store или на странице загрузок на сайте Apple Developer, но сначала убедитесь, что у вас есть активная учетная запись разработчика. Он довольно стабилен, поэтому вы можете заменить им Xcode 8 для повседневных задач кодирования.

Вы также можете установить несколько версий Xcode, используя xcversion

Если вы начинаете новый проект - все готово. Но если у вас уже есть проект, написанный на Swift 3.x - вам нужно пройти процесс миграции.

Рекомендуем сначала попробовать на Playground - привыкнуть к новым возможностям.

Вы заметите ссылки на предложения Swift Evolution в формате «SE -____» , когда читаете эту статью.

Переход на Swift 4

Миграция с одной основной версии Swift на другую всегда была довольно интенсивной, особенно со Swift 2.x на 3.0. Обычно на каждый проект уходит 1-2 дня, но переход на Swift 4 немного проще и его можно пройти намного быстрее.

Подготовка к миграции

Xcode 9 поддерживает не только Swift 4, но и переходную версию 3.2, поэтому ваш проект должен компилироваться без каких-либо серьезных трудностей. Это возможно, потому что компилятор Swift 4 и инструмент миграции поддерживают обе версии языка. Вы можете указать разные версии Swift для каждой цели, это очень полезно, если некоторые сторонние библиотеки еще не обновились или у вас есть несколько целей в вашем проекте. Однако не только язык, но и SDK тоже претерпели некоторые изменения, поэтому весьма вероятно, что некоторые обновления придется применить к вашему коду, поскольку Apple продолжает совершенствовать API SDK ...

Инструмент быстрой миграции

Как всегда, Apple предоставляет инструмент миграции Swift в составе Xcode, который может помочь в переходе с предыдущей версии Swift. Вы можете запустить его в Xcode, перейдя в Edit -> Convert -> To Current Swift Syntax… и выбрав цели, которые вы хотите преобразовать.

Затем вас спросят, какое предпочтение вывода Objective-C вы хотите применить:

Поскольку в Swift 4 преобладают аддитивные изменения, инструмент миграции на Swift будет управлять большинством изменений за вас.

Какао-стручки

Большинство разработчиков используют диспетчер зависимостей CocoaPods для своих проектов, поскольку диспетчер пакетов Swift не так развит, как мог бы, хотя он очень быстро улучшается. Как упоминалось выше, не все сторонние библиотеки были обновлены до Swift 4, поэтому вы могли видеть ошибки при компиляции некоторых из них. Одним из возможных решений этой проблемы является указание Swift версии 3.2 для тех модулей, которые еще не были обновлены, путем добавления сценария post_install в ваш Podfile :

 old_swift_3_pods = [ 'PodName1', 'PodName2', ] post_install do |installer| installer.pods_project.targets.each do |target| if old_swift_3_pods.include? target.name target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '3.2' end end end end

Тогда беги

 pod install

Теперь вы можете компилировать поды без ошибок.

Давайте рассмотрим изменения и дополнения в Swift 4 API.

Изменения и дополнения в API

Струны

String теперь соответствует протоколу Collection благодаря предложению SE-0163. Помните Swift 1.x?

Теперь нет необходимости в characters массива characters как вы можете напрямую перебирать String :

 let string = "Hello, Mind Studios!" for character in string { print(character) }

Это также означает, что вы можете использовать любые методы и свойства Collection в String , такие как count , isEmpty , map() , filter() , index(of:) и многие другие:

 string.count // No more `string.characters.count` string.isEmpty // false let index = string.index(of: " ") // 6 let reversedCollection = "abc".reversed() let reversedString = String(reversedCollection) // "cba" // String filtering let string = "ni123n456iniASijasod! 78a9-kasd aosd0" let numbersString = string.filter { Int(String($0)) != nil } // "1234567890"

Новый тип Substring

Swift 4 представляет новый тип Substring который представляет подпоследовательность String (как описано в SE-0163, упомянутом выше).

 // Split string into substrings let string = "Hello, Mind Studios!" let parts = string.split(separator: " ") // ["Hello,", "Mind", "Studios!"] type(of: parts.first!) // Substring.Type

И String и Substring теперь поддерживают новый StringProtocol что делает их почти идентичными и совместимыми:

 var hello = parts.first! // Concatenate a String onto a Substring hello += " !" // "Hello, !" // Create a String from a Substring let helloDog = String(hello) // "Hello, !"

Важная заметка

SE-0163 имеет очень важное замечание:

 Long-term storage of `Substring` instances is discouraged. A substring holds a reference to the entire storage of a larger string, not just to the portion it presents, even after the original string's lifetime ends. Long-term storage of a substring may therefore prolong the lifetime of elements that are no longer otherwise accessible, which can appear to be memory leakage.

Это означает, что Substring предназначена для использования в качестве временного хранилища для подпоследовательности String . Если вы хотите передать его каким-либо методам или другим классам - сначала преобразуйте его в String :

 let substring: Substring = ... // Substring let string = String(substring) // String someMethod(string)

В любом случае система типов Swift поможет вам не передавать Substring туда, где ожидается String (при условии, что вы не используете новый StringProtocol качестве типа параметра).

Многострочные строковые литералы

SE-0168 вводит простой синтаксис для многострочных строковых литералов с использованием трех двойных кавычек """ что означает, что большинство текстовых форматов (таких как JSON или HTML) или некоторый длинный текст могут быть вставлены без какого-либо экранирования:

 let multilineString = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam mattis lorem et leo laoreet fermentum. Mauris pretium enim ac mi tempor viverra et fermentum nisl. Sed diam nibh, posuere non lectus at, ornare bibendum erat. Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque. Vestibulum vulputate augue ac libero vulputate vestibulum. Nullam blandit et sapien non fermentum. Proin mollis nisl at vulputate euismod. """

Экранирование символов новой строки в строковых литералах

SE-0182 добавляет возможность экранировать символы новой строки в многострочных строковых литералах с помощью обратной косой черты в конце строки.

 let escapedNewline = """ Line 1, Line 2 \ next part of line 2, Line 3 """ print(escapedNewline)
 Line 1, Line 2 next part of line 2, Line 3

Улучшенная поддержка Unicode

Swift 4 обеспечивает поддержку Unicode 9, что означает, что проблемы с подсчетом символов Unicode теперь исчезли:

 "".count // 1, in Swift 3: 2 "".count // 1, in Swift 3: 2 "".count // 1, in Swift 3: 2 - person + skin tone "".count // 1, in Swift 3: 4 "".count // 3, in Swift 3: 1

Все изменения и реализованные предложения, выделенные выше (как и многие другие), берут свое начало из широко описанного набора функций, называемого строковым манифестом.

Контроль доступа

Swift 3 привнес в контроль доступа очень противоречивый элемент - модификатор доступа fileprivate , который может сбивать с толку.
Раньше модификатор уровня private доступа использовался для скрытия членов типа от других типов, а доступ к частным членам можно было получить только с помощью методов и свойств, определенных при определении типа, оставляя те же расширения типа в стороне, поскольку они не могли получить доступ к этим членам.
fileprivate можно использовать для совместного использования доступа для членов типа, таких как свойства и методы, в одном файле.
На самом деле использование private привело к проблеме, когда расширения какого-то типа не имели доступа к членам этого типа, поэтому использование fileprivate в таких обстоятельствах было очень распространенным решением, что привело к другой проблеме: другие типы в тот же файл может иметь доступ и к этим участникам.

Swift 4 наводит порядок, разрешая расширениям этого типа доступ к private членам этого типа в том же файле, как описано в SE-0169:

 struct User { private let firstName: String private let lastName: String } extension User: CustomStringConvertible { var description: String { return "User: \(firstName) \(lastName)" } }

Словарь и набор

Инициализация Dictionary с Sequence

Dictionary теперь можно инициализировать с помощью Sequence , но в этот инициализатор можно передать не все последовательности, а только те, которые содержат кортежи (Key, Value) , где Key - это тип ключа словаря, а Value представляет тип значения словаря:

 let stocksIdentifiers = ["AAPL", "GOOGL", "NKE"] let stocksValues = [158.28, 940.13, 53.73] let pairs = zip(stocksIdentifiers, stocksValues) let stocksValuesDict = Dictionary(uniqueKeysWithValues: pairs) // ["GOOGL": 940.13, "NKE": 53.73, "AAPL": 158.28]

Здесь zip функция создает пару ( Tuple s) из 2 последовательностей, вы можете узнать больше об этой функции в документации стандартной библиотеки Swift.

Объединение словарей

Вы можете указать, как должны обрабатываться повторяющиеся ключи при создании словаря из последовательности Tuple s, передав закрытие в параметр uniquingKeysWith , который используется для объединения значений из двух идентичных ключей.

Пример 1:

 let duplicates = [("a", 1), ("b", 5), ("a", 3), ("b", 3)] let dictionary = Dictionary(duplicates, uniquingKeysWith: { (first, _) in return first }) // ["b": 5, "a": 1]

Здесь мы оставляем первое значение, игнорируя все следующие значения с тем же ключом.

Пример 2:

Подсчет того, сколько раз каждый символ появляется в строке.

 let string = "Hello!" let pairs = Array(zip(string, repeatElement(1, count: string.count))) let counts = Dictionary(pairs, uniquingKeysWith: +) // ["H": 1, "e": 1, "o": 1, "l": 2, "!": 1]

Пример 3:

Используя метод merge :

 let values = ["a": 1, "b": 5] var additionalValues = ["b": 3, "c": 2, "a": 3] additionalValues.merge(values, uniquingKeysWith: +) // ["b": 8, "c": 2, "a": 4]

Подстрочный индекс со значением по умолчанию

Раньше обычной практикой было использование оператора объединения nil для присвоения значения по умолчанию в случае, если значение равно nil.

Swift 3:

 let dict = ["a": 1, "b": 5] dict["c"] ?? 0 // 0

Swift 4 вводит новое значение по default для индексов (часть SE-0165):

 let dict = ["a": 1, "b": 5] dict["c", default: 0] // 0, equals to `dict["c"] ?? 0` in Swift 3

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

 let string = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam mattis lorem et leo laoreet fermentum. Mauris pretium enim ac mi tempor viverra et fermentum nisl. Sed diam nibh, posuere non lectus at, ornare bibendum erat. Fusce mattis sem ac feugiat vulputate. Morbi at nunc maximus, vestibulum orci et, dictum neque. Vestibulum vulputate augue ac libero vulputate vestibulum. Nullam blandit et sapien non fermentum. Proin mollis nisl at vulputate euismod. """ var wordsCountByLine = [Int: Int]() let lines = string.split(separator: "\n") for (index, line) in lines.enumerated() { let lineWordsCount = line.split(separator: " ").count wordsCountByLine[index, default: 0] += lineWordsCount } print(wordsCountByLine) // [2: 10, 4: 15, 5: 7, 6: 6, 7: 6, 0: 8, 1: 7, 3: 10]

Карта и фильтр для конкретного словаря

Группировка элементов последовательности

Связанные типы с ограничениями в протоколах

Предложение SE-0142 вводит добавление условных предложений к объявлениям связанных типов.

 extension Sequence where Element: Numeric { var sum: Element { var result: Element = 0 for element in self { result += element } return result } } [1,2,3,4].sum

Архивирование и сериализация (кодирование / декодирование)

Раньше для сериализации какого-либо настраиваемого типа вам приходилось использовать старый и хорошо известный протокол NSCoding . Проблема в том, что неклассовые типы, такие как struct и enum не могут соответствовать этому протоколу, поэтому разработчикам не оставалось ничего другого, как использовать хаки, такие как обеспечение дополнительного уровня совместимости путем создания вложенного класса, который мог бы соответствовать NSCoding .

Swift 4 имеет очень удобное решение этой проблемы благодаря SE-0166 - введение протокола Codable :

 struct Employee: Codable { let name: String let age: Int let role: Role enum Role: String, Codable { case manager case developer case admin } } struct Company { let name: String let officeLocation: Location? let employees: [Employee] } struct Location : Codable { let latitude: Double let longitude: Double }

В простом случае , как это все , что вам нужно , это добавить Codable протокол соответствия для всех пользовательских типов, компилятор будет делать все волшебство для Вас. Вот и все!

Codable - это typealias для композиции протоколов Decodable и Encodable , поэтому вы можете объявить, например, только Decodable протоколу Decodable , если вы хотите декодировать свой экземпляр типа из данных JSON.

Кодирование

Если вы хотите сериализовать или десериализовать значение Codable - вы должны использовать объект кодировщика или декодера. Swift 4 уже поставляется с набором кодировщиков / декодеров для JSON и списков свойств, а также с новыми CocoaError для различных типов ошибок, которые могут возникать во время кодирования / декодирования. NSKeyedArchiver & NSKeyedUnarchiver также поддерживает Codable типы.

 let employee = Employee(name: "Peter", age: 27, role: .manager) let company = Company(name: "Awesome Company", officeLocation: nil, employees: [employee]) let encoder = JSONEncoder() let companyData = try encoder.encode(company) let string = String(data: companyData, encoding: .utf8)! print(string) >>> { "name" : "Awesome Company", "employees" : [ { "name" : "Peter", "age" : 27, "role" : "manager" } ] }

Кусок торта, не правда ли?

Расшифровка

Decoder используется для десериализации пользовательского типа Codable из Data . Он не знает, какой тип декодировать из самих данных, поэтому вы должны указать, какой тип декодировать, например, Employee или [Employee] :

 let decoder = JSONDecoder() let jsonData = """ [ { "name" : "Peter", "age" : 27, "role" : "manager" }, { "name" : "Alex", "age" : 26, "role" : "developer" }, { "name" : "Eugene", "age" : 30, "role" : "admin" } ] """.data(using: .utf8)! let employees = try decoder.decode([Employee].self, from: jsonData)
 If one of `Codable` type instances fails to decode, then whole collection will fail to decode.

Пользовательские имена ключей

В большинстве случаев имена, которые мы используем в пользовательских типах Swift, не соответствуют ключам в данных JSON, представляющих этот тип. Чтобы создать сопоставление между именами свойств настраиваемого типа и ключами JSON, вы можете создать вложенное перечисление с именем CodingKeys которое должно соответствовать протоколу CodingKey :

 struct Country: Decodable { let id: String let name: String let phoneCode: String private enum CodingKeys: String, CodingKey { case id = "alpha3" case name case phoneCode = "phone_code" } }

Пользовательское декодирование

Если у вас сложный случай, вы можете реализовать свой собственный инициализатор из протокола Decodable :

 struct Transaction { let id: Int let action: String let source: String let amount: Int let state: TransactionState let createdAt: Date let authorName: String enum TransactionState: String, Decodable { case done case canceled case processed } } extension Transaction: Decodable { private enum CodingKeys: String, CodingKey { case id case action = "action_name" case source = "source_name" case amount case state case createdAt = "created_at" case author } private enum AuthorKeys: String, CodingKey { case fullName = "full_name" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) actionName = try container.decode(String.self, forKey: .action) sourceName = try container.decode(String.self, forKey: .source) let createdAtValue = try container.decode(Double.self, forKey: .createdAt) createdAt = Date(timeIntervalSince1970: createdAtValue) state = try container.decode(TransactionState.self, forKey: .state) amount = try container.decodeIfPresent(Int.self, forKey: .amount) ?? 0 do { let authorContainer = try container.nestedContainer(keyedBy: AuthorKeys.self, forKey: .author) authorName = try authorContainer.decode(String.self, forKey: .fullName) } catch { authorName = "" } } }

Кодирование ключевого значения

Одна из удобных функций Swift 4 - Smart KeyPaths, описанная в SE-0161. В отличие от Swift 3 #keyPath() , который не является строго типизированным и работает только для членов Objective-C, Swift 4 KeyPath является универсальным классом, что означает, что ключевые пути теперь строго типизированы. Давайте рассмотрим несколько примеров:

 struct User { var username: String }

Общая форма ключевого пути \<Type>.<path> , где <Type> - это имя типа, а <path> - это цепочка из одного или нескольких свойств, например \User.username :

 let user = User(username: "max") let username = user[keyPath: \User.username] // "max"

Вы также можете записать новое значение по этому ключевому пути, если оно изменяемое:

 var user = User(username: "max") user[keyPath: \User.username] = "alex" // "alex"

Ключевые пути не ограничиваются одним уровнем иерархии:

 struct Comment { let content: String var author: User } let max = User(username: "max") let comment = Comment(content: "Nice post!", author: max) let authorUsername = comment[keyPath: \Comment.author.username] // "max"

Ключевые пути могут храниться в переменной:

 let authorKeyPath = \Comment.author let usernameKeyPath = authorKeyPath.appending(path: \.username) let authorUsername = comment[keyPath: usernameKeyPath] // "max"

Вы также можете использовать ключевые пути для необязательных и вычисляемых свойств:

 struct Post { let title: String var comments: [Comment] var topComment: Comment? { return comments.first } } let max = User(username: "max") let alex = User(username: "alex") var post = Post(title: "What's new in Swift 4", comments: []) let topCommentAuthorUsernameKeyPath = \Post.topComment?.author.username post[keyPath: topCommentAuthorUsernameKeyPath] // nil let comment = Comment(content: "", author: alex) let anotherComment = Comment(content: "Nice post!", author: max) post.comments = [comment, anotherComment] post[keyPath: topCommentAuthorUsernameKeyPath] // "alex"

Несмотря на то, что SE-0161 выделяет поддержку индексов в ключевых путях, они еще не реализованы:

 post.comments[keyPath: \.[0].content] // error: key path support for subscript components is not implemented let firstCommentAuthorKeyPath = \Post.comments[0].author // error: key path support for subscript components is not implemented

КВО

В дополнение к новым ключевым путям в Swift 4 также был обновлен API наблюдения за ключом.

 New KVO APIs depend on Objective-C runtime and works for `NSObject` subclasses only, so it can't be used for Swift structs and classes which don't inherit `NSObject`. In order to observe property it should be marked as `@objc dynamic var`.
 class User: NSObject { @objc dynamic var name: String var username: String init(name: String, username: String) { self.name = name self.userName = userName super.init() } } let user = User(name: "Max", username: "max") let nameObservation = user.observe(\.name, options: [.new, .old]) { user, change in // NSKeyValueObservation if let oldValue = change.oldValue, let newValue = change.newValue { print("fullName has changed from \(oldValue) to \(newValue)") } else { print("fullName is now \(user.name)") } } user.name = "Alex" // name has changed from Max to Alex

Вызовите метод invalidate() если хотите прекратить наблюдение

 nameObservation.invalidate() user.name = "Elina" // observer isn't get called

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

Односторонние диапазоны

SE-0172 представляет «односторонние» диапазоны, созданные с помощью префиксных / постфиксных версий существующих операторов диапазонов, и новый протокол RangeExpression для упрощения создания методов, которые принимают различные типы диапазонов.

Бесконечные последовательности

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

 let letters = ["a", "b", "c", "d"] let numberedLetters = Array(zip(1..., letters)) // [(1, "a"), (2, "b"), (3, "c"), (4, "d")]
 let string = "Hello, Mind Studios!" let index = string.index(of: ",")! string[..<index] // "Hello" string[...index] // "Hello,"

Использование односторонних диапазонов в сопоставлении с образцом

 let value = 5 switch value { case 1...: print("greater than zero") case 0: print("zero") case ..<0: print("less than zero") default: break }

Общие индексы

SE-0148 Индексы теперь могут иметь общие аргументы и возвращаемые типы.

 struct JSON { let data: [String: Any] subscript<T>(key: String) -> T? { return data[key] as? T } } let jsonDictionary: [String: Any] = [ "name": "Ukraine", "flag": "", "population": 42_500_000 ] let json = JSON(data: jsonDictionary) let population: Int? = json["population"] // 42500600
 extension Dictionary where Value == String { subscript<T: RawRepresentable>(key: Key) -> T? where T.RawValue == Value { guard let string = self[key] else { return nil } return T(rawValue: string) } } enum Color: String { case red case green case blue } let dictionary = [1: "red"] let color: Color? = dictionary[1] // red

Ограничение вывода Objective-C

Swift 4 минимизирует логический вывод @objc, ограничивая его только теми случаями, когда объявление должно быть доступно для Objective-C (SE-0160).
Это уменьшает двоичный размер вашего приложения, не компилируя избыточный код Objective-C, если вы его не используете, и дает больше контроля над тем, когда будет выводиться @objc. Производные классы NSObject больше не выводят @objc.

Но есть некоторые ситуации, в которых код Swift будет по-прежнему иметь неявный вывод:

  • Объявления с атрибутом @objc

  • Объявления, удовлетворяющие требованиям протокола @objc

  • Объявления с атрибутами @IBAction, @IBInspectable, @IBOutlet, @NSManaged, @GKInspectable

Чтобы включить вывод @objc для всего класса, вы можете использовать новый атрибут @objcmembers.
Чтобы отключить вывод @objc для определенного расширения или функции, добавьте новый атрибут @nonobjc.

Составление классов и протоколов

В Swift 4 теперь мы можем составлять протоколы вместе с другими типами Swift:

 User & Codable & CustomStringConvertible typealias MyType = User & Codable & CustomStringConvertible

Преимущества Swift 4.

Преимущества Swift 4 действительно огромны, как это часто бывает, когда Apple выпускает новую языковую версию. Помимо улучшенной языковой производительности, он также значительно стабилизировал процесс миграции. Возвращаясь к процессу миграции Swift 2.2 на 3.0, мы вспоминаем запутанный процесс переноса всех зависимостей. Изменения Swift 4.0 позволяют нам оставить сторонние библиотеки без фактического «перемещения» их - вам просто нужно обновить сам Swift.

Также, что касается улучшений Swift 4.0 и 3.0, размер скомпилированных двоичных файлов был изменен, что привело к уменьшению размера приложения; Например, мобильное приложение раньше весило 20 МБ, а в новейшей Swift-версии оно будет занимать около 17 МБ. И есть принципиальная разница между Swift 4 и Swift 3 - исправление ошибок произошло, и язык стал немного более быстрым.

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

Не пропустите нашу статью о MVP, MVC, MVVM и VIPER для разработки под iOS.

Авторы Макс Машков и Элина Бессарабова .