Оглавление
- Односторонние диапазоны
- Строки
- Приватные объявления видимы в экстеншенах того же файла
- Умные ключи пути
- Архивирование и сериализация
- Улучшения в 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()) // работает
GDXRepo
Отписал в личку по правкам. За пост спасибо!