Оглавление
- Односторонние диапазоны
- Строки
- Приватные объявления видимы в экстеншенах того же файла
- Умные ключи пути
- Архивирование и сериализация
- Улучшения в Dictionary и Set
- Метод MutableCollection.swapAt
- reduce с поддержкой inout
- Генеретики в сабскриптах
- Мостик для NSNumber
- Экземпляры классов и подтипов
Как это все запустить у себя?
- Скачать последний снепшот Swift 4 с сайта
- Запустить установщик
- Пройти в Xcode > Toolchains > Manage Toolchains и выбрать снепшот
Односторонние диапазоны
SE-0172 добавляет новый RangeExpression
протокол и набор префиксных/постфиксных операторов для определения односторонних диапазонов, то есть диапазоны, в которых либо нижняя, либо верхняя граница не определена
Бесконечные последовательности
Можно использовать одностороннюю последовательность, чтобы создать бесконечную последовательность, то есть более гибкая замена enumerated()
, когда не хочется, чтобы нумерация начиналась с нуля:
let letters = ["a","b","c","d"]
let numberedLetters = zip(1..., letters)
Array(numberedLetters)
Сабскрипты в коллекциях
Когда односторонняя последовательность используется в сабскрипте коллекции, то startIndex
или endIndex
“самозаполняют” в коллекции пропущенную верхнюю или нижнюю границу, соответсвенно.
let numbers = [1,2,3,4,5,6,7,8,9,10]
numbers[5...] // вместо numbers[5..<numbers.endIndex]
Сравнение паттернов
Это когда односторонняя последовательность используется в конструкции сравнения паттернов, например в case
или switch
. Обратите внимание, что компилятор пока не может определить, что switch
является здесь лишним.
let value = 5
switch value {
case 1...:
print("greater than zero")
case 0:
print("zero")
case ..<0:
print("less than zero")
default:
fatalError("unreachable")
}
Строки
Многострочные строковые литералы
SE-0168 вводит простой синтаксис для многострочных строковых литералов ("""
). В многострочном литерале не нужно экранировать одинарные кавычки, что означает, что такие форматы как JSON и HTML могут быть вставлены в них безо всякого экранирования. Отбивка закрывающего литерала определяет сколько пробелов будет удалено с начала каждой строки.
let multilineString = """
This is a multi-line string.
You don't have to escape "quotes" in here.
The position of the closing delimiter
controls whitespace stripping.
"""
print(multilineString)
Чтобы увидеть результат работы print
можно вывести консоль нажав (View > Debug Area > Activate Console).
Строка теперь опять коллекция
SE-0163 является первой частью пересмотренной строковой модели для Swift 4. Самое большое изменение, что теперь строка — это коллекция (как раньше было в Swift 1.x), то есть функциональность String.CharacterView
была свернута в родительский тип. (Другие виды, UnicodeScalarView
, UTF8View
, и UTF16View
, по прежнему присутствуют.)
Обратите внимание, что SE-0163 еще не полностью реализован, и в будущем будут более строгие изменения.
let greeting = "Hello, !"
// теперь не нужно опускаться до .characters
greeting.count
for char in greeting {
print(char)
}
Substring — новый тип для слайсов строк
Экземпляры слайса строки теперь являются типом Substring
. Оба типа String
и Substring
реализуют протокол StringProtocol
. Почти все API для строк живет в StringProtocol
поэтому String
и StringProtocol
в основном ведут себя одинаково.
let comma = greeting.index(of: ",")!
let substring = greeting[..<comma]
type(of: substring)
// API от String можно использовать в Substring
print(substring.uppercased())
Unicode 9
Swift 4 будет поддерживать Unicode 9, исправлены проблемы с надлежащей кластеризацией графем для современных эмодзи. Всё указанное ниже теперь являются одним символом:
"".count // person + skin tone
"".count // family with four members
"u{200D}u{200D}u{200D}".count // family + skin tones
"".count // person + skin tone + profession
Хабрапарсер сожрал все эмодзи, с ними смотреть тут
Свойство Character.unicodeScalars
Теперь можно получить доступ к точкам Character
напрямую без превращения их в строку (SE-0178).
let c: Character = ""
Array(c.unicodeScalars)
Приватные объявления видимы в экстеншенах того же файла
SE-0169 изменяет правила контроля доступа так, что теперь приватные объявления видимы в экстеншенах родительского типа в том же файле. Это позволяет разбить определение вашего типа на несколько экстеншенов и по-прежнему использовать приватный доступ для большинства «приватных» вещей, уменьшая потребность в использовании ключа доступа fileprivate
.
struct SortedArray<Element: Comparable> {
private var storage: [Element] = []
init(unsorted: [Element]) {
storage = unsorted.sorted()
}
}
extension SortedArray {
mutating func insert(_ element: Element) {
// storage тут доступен
storage.append(element)
storage.sort()
}
}
let array = SortedArray(unsorted: [3,1,2])
// storage _не_ доступен тут (в отличии от fileprivate)
//array.storage // error: 'storage' is inaccessible due to 'private' protection level
Умные ключи пути
Вероятно, одна из главных особенностей Swift 4 это новая модель ключей пути (key path) описанная в SE-0161. В отличии от строковых ключей пути в Cocoa, в Swift ключи пути строго типизированные.
struct Person {
var name: String
}
struct Book {
var title: String
var authors: [Person]
var primaryAuthor: Person {
return authors.first!
}
}
let abelson = Person(name: "Harold Abelson")
let sussman = Person(name: "Gerald Jay Sussman")
let sicp = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman])
Ключи пути можно указывать, начиная с корневого типа и опускаться до любой комбинации свойств и имен.
Написание ключа пути начинается с бэкслеша: Book.title
. Любой тип в Swift принимает [keyPath: …]
— сабскрипт для получения или установки значения для нужного ключа пути.
sicp[keyPath: Book.title]
// Ключи пути могут работать с вычисляемыми свойствами
sicp[keyPath: Book.primaryAuthor.name]
Ключи пути — это объект KeyPath
, который можно хранить и производить манипуляции с ним. Например, можно добавить дополнительные сегменты к ключу пути, чтоб углубиться дальше.
let authorKeyPath = Book.primaryAuthor
type(of: authorKeyPath)
let nameKeyPath = authorKeyPath.appending(path: .name) // можно опустить тип имени если компилятор может его вычислить
sicp[keyPath: nameKeyPath]
Сабскрипты в ключах путей
В ключах пути так же можно использовать сабскрипт нотацию. Это делает их очень удобными для работы с коллекциям, массивам или словарям. Эта функциональность пока еще не реализована в текущем снэпшоте.
//sicp[keyPath: Book.authors[0].name]
// INTERNAL ERROR: feature not implemented: non-property key path component
Архивирование и сериализация
SE-0166: Swift Archival & Serialization определяет как типы в Swift (классы, структуры, и енумы) будут сериализовывать и архивировать себя. Типы могут сделать себя (раз-)архивируемыми реализовав протокол Codable
.
В большинстве случаев имплементация Codable
протокола — все что требуется, компилятор может сгенерировать остальную часть имплементации сам, только если все члены типа реализуют Codable
. Так же можно переопределить стандартное поведение, если нужно поменять то как тип себя сериализует. В этой теме есть много нюансов — обязательно ознакомьтесь с предложением для уточнения деталей.
// Делаем свой тип сериализуемым (и всех его членов) унаследовав протокол Codable
struct Card: Codable {
enum Suit: String, Codable {
case clubs, spades, hearts, diamonds
}
enum Rank: Int, Codable {
case ace = 1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king
}
var suit: Suit
var rank: Rank
}
let hand = [Card(suit: .clubs, rank: .ace), Card(suit: .hearts, rank: .queen)]
Кодирование
Когда у вас есть значение реализующее Codable
, нужно передать его кодировщику, чтобы заархивировать.
Вы можете написать свои кодеры и декодеры, которые используют инфраструктуру от Codable
, но в Swift будут поставляться встроенные для JSON (JSONEncoder
и JSONDecoder
) и для списка свойств (PropertyListEncoder
и PropertyListDecoder
). Они определены в SE-0167. NSKeyedArchiver
так же будет поддерживать все Codable
типы
import Foundation
var encoder = JSONEncoder()
// Свойства предоставляемые JSONEncoder для кастомизации вывода
encoder.dataEncodingStrategy
encoder.dateEncodingStrategy
encoder.nonConformingFloatEncodingStrategy
encoder.outputFormatting
encoder.userInfo
let jsonData = try encoder.encode(hand)
String(data: jsonData, encoding: .utf8)
Декодирование
let decoder = JSONDecoder()
let decoded = try decoder.decode([Card].self, from: jsonData)
Улучшения в Dictionary
и Set
SE-0165 добавляет несколько улучшений для Dictionary
и Set
.
Инициализатор принимающий последовательность
Создание словаря из последовательности пар ключ-значение.
let names = ["Cagney", "Lacey", "Bensen"]
let dict = Dictionary(uniqueKeysWithValues: zip(1..., names))
dict[2]
Инициализатор слияния и метод слияния
Теперь можно определить то, как дубли ключей будут обработаны, когда создается словарь из последовательности или производится слияние последовательности в текущий словарь.
let duplicates = [("a", 1), ("b", 2), ("a", 3), ("b", 4)]
let letters = Dictionary(duplicates, uniquingKeysWith: { (first, _) in first })
letters
let defaults = ["foo": false, "bar": false, "baz": false]
var options = ["foo": true, "bar": false]
// Этот код упадет с ошибкой типизации: error: generic parameter 'S' could not be inferred
// Я надеюсь что это относится к https://bugs.swift.org/browse/SR-922
//options.merge(defaults) { (old, _) in old }
Сабскрипт со значением по умолчанию
Можно определить значччение по умолчанию для несуществующих ключей, как аргумент сабскрипта, сделав возвращаемый тип не опциональным.
dict[4, default: "(unknown)"] // вернется значение которе не нужно анврапить
Это особенно важно когда нужно мутировать значение через сабскрипт:
let source = "how now brown cow"
var frequencies: [Character: Int] = [:]
for c in source {
frequencies[c, default: 0] += 1
}
frequencies
Специфичные для словаря map
и filter
filter
возвращает Dictionary
а не Array
. Аналогично, новый метод mapValues
преобразует значения c сохранением его структуры
let filtered = dict.filter {
$0.key % 2 == 0
}
type(of: filtered)
let mapped = dict.mapValues { value in
value.uppercased()
}
mapped
Set.filter
так же возвращает Set
а не Array
.
let set: Set = [1,2,3,4,5]
let filteredSet = set.filter { $0 % 2 == 0 }
type(of: filteredSet)
Группировка последовательности
Группировка последовательности значений в букеты. разбиваем слова в списке по их первой букве.
let contacts = ["Julia", "Susan", "John", "Alice", "Alex"]
let grouped = Dictionary(grouping: contacts, by: { $0.first! })
grouped
Метод MutableCollection.swapAt
SE-0173 представляет новый метод для обмена двух элементов в коллекции. В отличии от существующего swap(_:_:)
, метод swapAt(_:_:)
принимает индексы элементов, которые нужно обменять, а не сами элементы (через inout
аргументы).
Причина для добавления этого метода в том, что обмен с двумя inout
аргументами несовместим
с новым правилами доступа к памяти SE-0176. Существующая функция swap(_:_:)
больше не будет работать для обмена двух элементов одной и той же коллекции.
var numbers = [1,2,3,4,5]
numbers.swapAt(0,1)
// Will be illegal in Swift 4 (not implemented yet)
swap(&numbers[3], &numbers[4])
numbers
reduce
с поддержкой inout
SE-0171 добавляет вариант reduce
метода в котором результат передается как inout
в функцию combine
. Это может быть существенным ускорением для алгоритмов которые используют reduce
чтобы инкрементально строить последовательности, путем исключения копирования и промежуточного результата.
SE-0171 пока что не реализован
// Пока что не работает
extension Sequence where Iterator.Element: Equatable {
func uniq() -> [Iterator.Element] {
return reduce(into: []) { (result: inout [Iterator.Element], element) in
if result.last != element {
result.append(element)
}
}
}
}
[1,1,1,2,3,3,4].uniq()
Генеретики в сабскриптах
Как представлено в SE-0148, сабскрипт теперь может принимать и возвращать аргументы в виде генериков.
Канонический пример — это тип который предсталяет JSON
данные: можно определить сабскрипт с генериком, чтобы контекст, вызывающего кода смог определить ожидаемый возвращаемый тип.
struct JSON {
fileprivate var storage: [String:Any]
init(dictionary: [String:Any]) {
self.storage = dictionary
}
subscript<T>(key: String) -> T? {
return storage[key] as? T
}
}
let json = JSON(dictionary: [
"name": "Berlin",
"country": "de",
"population": 3_500_500
])
// Теперь не нужно использовать as? Int
let population: Int? = json["population"]
Другой пример: сабскрипт в Collection
, который принимает последовательность индексов и возвращает массив значений этих индексов.
extension Collection {
subscript<Indices: Sequence>(indices indices: Indices) -> [Iterator.Element] where Indices.Iterator.Element == Index {
var result: [Element] = []
for index in indices {
result.append(self[index])
}
return result
}
}
let words = "Lorem ipsum dolor sit amet".split(separator: " ")
words[indices: [1,2]]
Мостик для NSNumber
SE-0170 исправляет некоторое опасное поведение с мостом между числовым типом в Swift и NSNumber
.
import Foundation
let n = NSNumber(value: UInt32(543))
let v = n as? Int8 // nil in Swift 4. This would be 31 in Swift 3 (try it!).
Экземпляры классов и подтипов
Теперь можно писать эквивалент кода на Objective-C UIViewController <SomeProtocol> *
в Swift,
например объявить переменную с конкретным типом и связать её к одному или нескольким протоколам одновременно (SE-0156). Синтаксис let variable: SomeClass & SomeProtocol1 & SomeProtocol2
import Cocoa
protocol HeaderView {}
class ViewController: NSViewController {
let header: NSView & HeaderView
init(header: NSView & HeaderView) {
self.header = header
super.init(nibName: nil, bundle: nil)!
}
required init(coder decoder: NSCoder) {
fatalError("not implemented")
}
}
// Нельзя передать просто NSView который не реализует протокол
// ViewController(header: NSView())
// error: argument type 'NSView' does not conform to expected type 'NSView & HeaderView'
// Должен пройти как NSView (сабкласс) который так же реализует протокол
extension NSImageView: HeaderView {}
ViewController(header: NSImageView()) // работает
Автор: JiLiZART