Что нового нас ждет в Swift 4?

https://github.com/ole/whats-new-in-swift-4
  • Перевод

Оглавление


  1. Односторонние диапазоны
  2. Строки
  3. Приватные объявления видимы в экстеншенах того же файла
  4. Умные ключи пути
  5. Архивирование и сериализация
  6. Улучшения в Dictionary и Set
  7. Метод MutableCollection.swapAt
  8. reduce с поддержкой inout
  9. Генерики в сабскриптах
  10. Мостик для NSNumber
  11. Экземпляры классов и подтипов

Как это все запустить у себя?


  1. Скачать последний снепшот Swift 4 с сайта
  2. Запустить установщик
  3. Пройти в 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()) // работает
Метки:
  • +23
  • 13,3k
  • 6
Поделиться публикацией
Похожие публикации
Комментарии 6
  • 0
    Отписал в личку по правкам. За пост спасибо!
    • 0
      Спасибо
      • 0
        Очень рад, что вернули доступ к private из расширений.
        • 0

          Спасибо

          • 0
            > Генеретики в сабскриптах
            генерики, видимо

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.