- PVSM.RU - https://www.pvsm.ru -

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

Оглавление

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

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

  1. Скачать последний снепшот Swift 4 с сайта [12]
  2. Запустить установщик
  3. Пройти в Xcode > Toolchains > Manage Toolchains и выбрать снепшот

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

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

SE-0172 [13] добавляет новый 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 [14] вводит простой синтаксис для многострочных строковых литералов ("""). В многострочном литерале не нужно экранировать одинарные кавычки, что означает, что такие форматы как 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 [15] является первой частью пересмотренной строковой модели для 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

Хабрапарсер сожрал все эмодзи, с ними смотреть тут [16]

Свойство Character.unicodeScalars

Теперь можно получить доступ к точкам Character напрямую без превращения их в строку (SE-0178 [17]).

let c: Character = ""
Array(c.unicodeScalars)

Приватные объявления видимы в экстеншенах того же файла

SE-0169 [18] изменяет правила контроля доступа так, что теперь приватные объявления видимы в экстеншенах родительского типа в том же файле. Это позволяет разбить определение вашего типа на несколько экстеншенов и по-прежнему использовать приватный доступ для большинства «приватных» вещей, уменьшая потребность в использовании ключа доступа 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 [19]. В отличии от строковых ключей пути в 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 [20] определяет как типы в 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 [21]. 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 [22] добавляет несколько улучшений для 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 [23] представляет новый метод для обмена двух элементов в коллекции. В отличии от существующего swap(_:_:), метод swapAt(_:_:) принимает индексы элементов, которые нужно обменять, а не сами элементы (через inout аргументы).

Причина для добавления этого метода в том, что обмен с двумя inout аргументами несовместим
с новым правилами доступа к памяти SE-0176 [24]. Существующая функция 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 [25] добавляет вариант 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 [26], сабскрипт теперь может принимать и возвращать аргументы в виде генериков.

Канонический пример — это тип который предсталяет 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 [27] исправляет некоторое опасное поведение с мостом между числовым типом в 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 [28]). Синтаксис 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

Источник [29]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/256635

Ссылки в тексте:

[1] Односторонние диапазоны: #odnostoronnie-diapazony

[2] Строки: #stroki

[3] Приватные объявления видимы в экстеншенах того же файла: #privatnye-obyavleniya-vidimy-v-ekstenshenah-togo-zhe-fayla

[4] Умные ключи пути: #umnye-klyuchi-puti

[5] Архивирование и сериализация: #arhivirovanie-i-serializaciya

[6] Улучшения в Dictionary и Set: #uluchsheniya-v-dictionary-i-set

[7] Метод MutableCollection.swapAt: #metod-mutablecollectionswapat

[8] reduce с поддержкой inout: #reduce-s-podderzhkoy-inout

[9] Генеретики в сабскриптах: #generetiki-v-sabskriptah

[10] Мостик для NSNumber: #mostik-dlya-nsnumber

[11] Экземпляры классов и подтипов: #ekzemplyary-klassov-i-podtipov

[12] Скачать последний снепшот Swift 4 с сайта: https://swift.org/download/#snapshots

[13] SE-0172: https://github.com/apple/swift-evolution/blob/master/proposals/0172-one-sided-ranges.md

[14] SE-0168: https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

[15] SE-0163: https://github.com/apple/swift-evolution/blob/master/proposals/0163-string-revision-1.md

[16] смотреть тут: https://gist.github.com/JiLiZART/c0bfe8280fc0802ac634c68880e756a8

[17] SE-0178: https://github.com/apple/swift-evolution/blob/master/proposals/0178-character-unicode-view.md

[18] SE-0169: https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md

[19] SE-0161: https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md

[20] SE-0166: Swift Archival & Serialization: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

[21] SE-0167: https://github.com/apple/swift-evolution/blob/master/proposals/0167-swift-encoders.md

[22] SE-0165: https://github.com/apple/swift-evolution/blob/master/proposals/0165-dict.md

[23] SE-0173: https://github.com/apple/swift-evolution/blob/master/proposals/0173-swap-indices.md

[24] SE-0176: https://github.com/apple/swift-evolution/blob/master/proposals/0176-enforce-exclusive-access-to-memory.md

[25] SE-0171: https://github.com/apple/swift-evolution/blob/master/proposals/0171-reduce-with-inout.md

[26] SE-0148: https://github.com/apple/swift-evolution/blob/master/proposals/0148-generic-subscripts.md

[27] SE-0170: https://github.com/apple/swift-evolution/blob/master/proposals/0170-nsnumber_bridge.md

[28] SE-0156: https://github.com/apple/swift-evolution/blob/master/proposals/0156-subclass-existentials.md

[29] Источник: https://habrahabr.ru/post/329580/