company_banner

Swift 5.2. Обзор всех изменений

    В конце марта вышел релиз Swift 5.2 для Xcode 11.4. В нём улучшена диагностика ошибок, анализ зависимостей, расширен функционал SwiftPM. Обзор некоторых изменений уже был опубликован на Хабре, в этой же статье рассмотрена эволюция самого языка с возможными примерами использования.


     

    SE-0249 KeyPath как функция


    Шорткат кастит keyPath в функцию, где входящий параметр — сам объект, а результат — его свойство.

    Давайте смотреть на примере. Создадим массив из простых моделей:

    struct Model {
        let isHidden: Bool
    }
     
    let modelArray = [Model(isHidden: true), Model(isHidden: false)]
    

    Отфильтруем массив по свойству isHidden. Ниже приведены 3 примера с одинаковым результатом:

    // 1
    let funcIsHidden: (Model) -> Bool = \.isHidden
    modelArray.filter(funcIsHidden)
     
    // 2
    modelArray.filter(\.isHidden as (Model) -> Bool)
     
    // 3
    modelArray.filter(\.isHidden)
    

    Заявленная функциональность не работает в следующем примере:

    //  Валидный код
    let selfFunc: (Model) -> Model = \.self
    modelArray.compactMap(selfFunc)
     
    // ERROR: Expression type '(Model) -> Model' is ambiguous without more context
    modelArray.compactMap(\.self as (Model) -> Model)
     
    // ERROR: Key path value type 'Optional<_>' cannot be converted to contextual type '_'
    modelArray.compactMap(\.self)
    

    Также, в отличие от keyPath, не работает автокомплит.

    Удобно использовать для работы с массивами в таких функциях, как filter, map, reduce, sort и аналогичных.

    SR-6118 Subscripts может содержать параметры по умолчанию


    Всем параметрам функции можно задать значение по умолчанию. Создадим структуру Box, которая содержит массив элементов, и функцию subscript для обращения к ним.

    struct Box {
        let items: [String]
        
        subscript(_ index: Int = 0) -> String {
            items[index]
        }
    }
    

    Теперь для обращения к первому элементу можно опустить индекс:

    let box = Box(items: ["laptop, , mobile phone"])
    let firstItem = box[] // "laptop"
    

    SR-2189 Локальные функции поддерживают параметры по умолчанию из внешнего предела видимости


    В повседневной практике локальные функции используются редко. Оправдать их использование бывает сложно. Тем не менее, допускаю, что в определённых случаях это может пригодиться.

    Для примера создадим функцию, внутри которой опишем локальную:

    func getXPosition() {
        func calculateWidth(x: CGFloat = minX) -> CGFloat { ... }
        let minX: CGFloat = 0
        ...
    }
    

    В качестве параметра по умолчанию для функции calculateWidth можем использовать значения в границах функции getXPosition.

    SE-0253 Использование значений в качестве функций


    Функциональность, аналогично @dynamicCallable, позволяет использовать значение в качестве функции. Но является реализацией «статического вызова».

    На практике всё крайне просто: для вызова функций можно обращаться к значениям как к методам.

    Создадим структуру Player:

    struct Player {
        private(set) var isRunning = false
        
        mutating func callAsFunction() {
            isRunning.toggle()
        }
    }
    

    Создадим экземпляр Player и обратимся к нему как к функции:

    var player = Player()
    print(player.isRunning) // false
    player()
    print(player.isRunning) // true
    

    При этом запрещено кастить, а значит, и передавать объект как функцию:

    // ERROR: Cannot convert value of type 'Player' to type '() -> Void' in coercion
    let playerAsFunc = player as () -> Void
    

    Можно добавить сколько угодно методов с названием callAsFunction к классу, структуре или протоколу:

    extension Player {
        func callAsFunction(isRunning: Bool = false) throws { ... }
        func callAsFunction() -> Bool { ... }
    }
    

    Применение возможно в значениях, которые представляют собой математические функции, сложные выражения или же в случаях, где значение имеет одну доминирующую функцию. Всё же не стоит злоупотреблять данным функционалом, так как он может вводить в заблуждение.

    SR-4206 Исправлен баг с переопределением функции с generic-параметром


    Теперь, переопределяя функцию, нельзя изменить или добавить ограничения на generic-тип. В качестве примера создадим класс CarBuilder и наследуемся от него, переопределив метод add:

    protocol PowerfullEngine { }
     
    class CarBuilder {
      func add<T>(engine: T) {}
    }
     
    class MercedesBuilder: CarBuilder {
        // Валидный код
        override func add<T>(engine: T) {}
        // ERROR: Overridden method 'add' has generic signature <T where T : PowerfullEngine> which is incompatible with base method's generic signature <T>; expected generic signature to be <T>
        override func add<T: PowerfullEngine>(engine: T) {}
    }
    

    SR-11298 Расширение протокола без ограничений на класс наследует это ограничение


    В случае наложения ограничения на Self, расширение применит ограничение.

    К примеру, создадим протокол Menu и extension с ограничением по классу:

    protocol Menu {}
     
    class SideMenu: Menu {
      var sectionHeight = 0
    }
     
    extension Menu where Self: SideMenu {
      var menuHeight: Int {
        get { sectionHeight * 20 }
        set { sectionHeight = newValue / 20 }
      }
    }
    

    При этом сеттер — nonmutating, как будто протокол имеет ограничение на класс.

    SR-11429 Кастинг для функций с лейблами


    При кастинге функции к типу лейблы теперь убираются. Благодаря этому можно кастить функции с лейблами. Прежде данная функциональность работала только в функциях без лейблов:

    func setX(x: Int) {}
    (setX as (Int) -> Void)(5)
    

    При этом значения по умолчанию сохранить не выйдет:

    func setPoint(x: Int, y: Int = 0) {}
    (setPoint as (Int, Int) -> Void)(5, 1)
    

    Это применимо и к generic-ам, поэтому данный код больше не валиден.

    typealias Point<T> = T
    func set(x: Int) {}
     
    // Валидный код
    (set as Point)(5) 
     
    // ERROR: Extraneous argument label 'x:' in call
    (set as Point)(x: 5) 
    

    SR-11841 Функция filter(_:) вызывается в ожидаемом порядке в lazy-коллекциях


    Создадим lazy-коллекцию и вызовем метод count:

    let lazyArray = [0]
        .lazy
        .filter { _ in print("A"); return true }
        .filter { _ in print("B"); return true }
     
    // Результат A B
    _ = lazyArray.count
    

    Прежде результат вызова был обратный: B A.

    SR-2790 Ряд инициализаторов типов семейства UnsafePointer / UnsafeBufferPointer теперь выдаёт предупреждение


    Ограничение действует на строки, массивы и inout-параметры, которые не существуют за пределами вызова функции.

    Создадим структуру, принимающую UnsafePointer в качестве параметра при инициализации:

    struct Game {
        let info: UnsafePointer<Int8>
    }
     
    func createGame() {
        var i: Int8 = 0
        
        // WARNING: Initialization of 'UnsafePointer<Int8>' results in a dangling pointer
        _ = Game(info: .init(&i))
        
        // WARNING: Passing '[Int8]' to parameter, but argument 'character' should be a pointer that outlives the call to 'init(character:)'
        _ = Game(info: [1, 2])
        
        // WARNING: Passing 'String' to parameter, but argument 'character' should be a pointer that outlives the call to 'init(character:)
        _ = Game(info: "")
    }
    

    Всего изменений в этой версии было 9, тем не менее, они определённо внесли новую функциональность. Предполагаю, что самым используемым из текущих будет KeyPath как функция.

    А мы с нетерпением ждём следующую версию. Судя по всему, в ней будет поддержка Swift на Windows и появятся интересные фичи, такие как использование self в escaping-замыканиях без обращения через self.* (SE-0269) и расширится функциональность generic-ов (SE-0267).

    Источник
    FunCorp
    Разработка развлекательных сервисов

    Похожие публикации

    Комментарии 7

      +1
      Мне как любителю языков программирования стало интересно что это за синтаксис такой
      \.isHidden

      Вероятно я что-то упустил, первый раз такое вижу:)
        +2
        Хорошо описано здесь. Это ссылка на свойство
          0
          Спасибо. Похоже, это что-то вроде указателей на члены класса в C++. Вообще пока смотрел примеры, нашел в Swift'е столько разных интересных фич, что думаю нужно прочитать какую-то последнюю книгу по языку.
            +1
            Пожалуйста ) В книгах фич не так много на единицу текста как в блогах к примеру. Все же они для углубления в определенную область
        0
        Ох, вот чую, что с «Subscripts может содержать параметры по умолчанию» намучается еще не один человек. Уж больно близко конструкция box[] для доступа к n-ому элементу подходит к грани неопределеного поведения. А ведь истина может быть зарыта где-то далеко в Extension…
          0
          Конструкция неоднозначная, но для поиска истины ) достаточно зажав command кликнуть на одну из скобок и с помощью контекстного меню перейти к определению
            0
            Тут может сыграть злую шутку ложная память: «точно помню, что тут именно так». Лень же смотреть, ну или в пылу угара «депплой должен быть вчера» :)

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

        Самое читаемое