Русский
Русский
English
Статистика
Реклама

Property wrappers

Перевод Новые Property Wrappers в SwiftUI

27.07.2020 20:17:32 | Автор: admin

Привет, Хабровчане! В конце августа в OTUS стартует новая группа профессионального базового курса Разработчик iOS. Как всегда делимся полезным переводом и приглашаем на бесплатные онлайн-мероприятия: День Открытых Дверей и Быстрый старт в IOS-разработку.




WWDC20 привнес в SwiftUI много новых функций, о которых на протяжении следующих недель я собираюсь рассказывать вам в своем блоге. Начать же сегодня я хочу с главных дополнений к потокам данных SwiftUI в виде новых оберток свойств (Property Wrappers) @StateObject, @AppStorage, @SceneStorage и @ScaledMetric.


Если вы не знакомы с существующими обертками свойств, которые предоставляет SwiftUI, я советую вам начать с публикации Понимаем Property Wrappers в SwiftUI.

StateObject


Как вы помните, SwiftUI предоставляет нам обертку свойства @ObservedObject, которая позволяет нам наблюдать за изменениями в модели данных, которая находится за пределами инфраструктуры SwiftUI. Например, это могут быть данные, которые вы извлекаете из веб-службы или локальной базы данных. Основной проблемой @ObservedObject был жизненный цикл. Вы должны хранить его где-то за пределами SwiftUI, чтобы сохранить его во время обновлений представления, например, в SceneDelegate или AppDelegate. Иначе при определенных обстоятельствах вы можете потерять данные, за которые отвечает @ObservedObject.


Новая обертка свойства StateObject исправляет самый существенный пробел в управлении потоками данных SwiftUI. SwiftUI создает только один экземпляр StateObject для каждого объявленного вами экземпляра контейнера и сохраняет его во внутренней памяти фреймворка, которая сохраняет его при обновлении представления. StateObject работает очень похоже на обертку свойства State, но вместо типов значений он предназначен для работы со ссылочными типами.


struct CalendarContainerView: View {    @StateObject var viewModel = ViewModel()    var body: some View {        CalendarView(viewModel.dates)            .onAppear(perform: viewModel.fetch)    }}

AppStorage


AppStorage еще одна новая обертка свойства, которую мы получили в этом году. Это идеальный способ хранения небольшого количества данных вида ключ-значение связанных с UserDefaults. AppStorage DynamicProperty, что означает, что SwiftUI обновит ваши представления, как только значение отдельно взятого ключа обновится в UserDefaults. AppStorage идеально подходит для хранения настроек приложения. Давайте посмотрим, как мы можем ее использовать.


enum Settings {    static let notifications = "notifications"    static let sleepGoal = "sleepGoal"}struct SettingsView: View {    @AppStorage(Settings.notifications) var notifications: Bool = false    @AppStorage(Settings.sleepGoal) var sleepGoal: Double = 8.0    var body: some View {        Form {            Section {                Toggle("Notifications", isOn: $notifications)            }            Section {                Stepper(value: $sleepGoal, in: 6...12) {                    Text("Sleep goal is \(sleepGoal, specifier: "%.f") hr")                }            }        }    }}

В приведенном выше примере мы создаем экран настроек, используя обертку свойства AppStorage и представление Form. Теперь мы можем получить доступ к нашим настройкам в любом месте приложения с помощью AppStorage, и как только мы изменим там какие-либо значения, SwiftUI обновит представления.


struct ContentView: View {    @AppStorage(Settings.sleepGoal) var sleepGoal = 8    @StateObject var store = SleepStore()    var body: some View {        WeeklySleepChart(store.sleeps, goal: sleepGoal)            .onAppear(perform: store.fetch)    }}

SceneStorage


В этом году мы получили множество возможностей для управления сценами в SwiftUI без UIKit. В результате у нас появилась новая обертка свойства SceneStorage, которая позволяет нам реализовать правильное восстановление состояния для наших сцен. SceneStorage работает аналогично AppStorage, но вместо UserDefaults использует отдельное хранилище для каждой сцены. Это означает, что каждая сцена имеет свое собственное хранилище, к которому не могут получить доступ другие сцены. Система полностью отвечает за управление хранилищем для каждой сцены, и у вас нет доступа к данным без обертки свойства SceneStorage.


struct ContentView: View {    @SceneStorage("selectedTab") var selection = 0    var body: some View {        TabView(selection: $selection) {            Text("Tab 1").tag(0)            Text("Tab 2").tag(1)        }    }}

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


ScaledMetric


Еще одна новая обертка свойства ScaledMetric. ScaledMetric позволяет нам масштабировать любое двоичное плавающее значение относительно категории размера Dynamic Type. Например, в вашем приложении можно очень легко изменить интервалы в соответствии с категорией размера Dynamic Type. Давайте посмотрим на небольшой пример.


struct ContentView: View {    @ScaledMetric(relativeTo: .body) var spacing: CGFloat = 8    var body: some View {        VStack(spacing: spacing) {            ForEach(0...10, id: \.self) { number in                Text(String(number))            }        }    }}

Как только пользователь изменит настройки Dynamic Type, SwiftUI масштабирует значение интервала и обновит представление.


Заключение


Сегодня мы узнали о новых обертках свойств в SwiftUI. Я считаю, что теперь мы имеем достаточно оберток свойств потоков данных, чтобы охватить любую логику, которая нам потребуется при реализации наших приложений. Не стесняйтесь подписываться на меня в Twitter и задавать свои вопросы, связанные с этой статьей. Спасибо за внимание и хорошей вам недели!




Узнать подробнее о базовом курсе Разработчик iOS



Подробнее..

Перевод Обертки свойств в Swift с примерами кода

20.05.2021 20:12:40 | Автор: admin

Перевод статьи подготовлен в рамках онлайн-курса "iOS Developer. Professional". Если вам интересно узнать подробнее о курсе, приходите на День открытых дверей онлайн.


Property Wrappers (Обертки Свойств) в Swift позволяют извлекать общую логику в отдельный объект-обертку. С момента представления во время WWDC 2019 и появления в Xcode 11 со Swift 5 было много примеров, которыми поделились в сообществе. Это изящное дополнение к библиотеке Swift, позволяющее удалить много шаблонного кода, который, вероятно, все мы писали в своих проектах.

Историю об обертках свойств можно найти на форумах Swift для SE-0258. В то время как целесообразность их использования в основном говорит о том, что обертки свойств являются решением для @NSCopying свойств, есть общая закономерность, которая реализовывается ими, и вы, вероятно, скоро все узнаете.

Что такое обертка свойства?

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

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

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool}

Оператор @UserDefault выполняет вызов обертки свойства. Как видите, мы можем задать ему несколько параметров, которые используются для настройки обертки свойства. Существует несколько способов взаимодействия с оберткой свойства, например, использование обернутого и прогнозируемого значения. Вы также можете настроить обертку с внедренными свойствами, о которых мы поговорим позже. Давайте сначала рассмотрим пример обертки свойства User Defaults.

Обертки свойств и UserDefaults

В следующем коде показан шаблон, который легко узнать. Он создает обертку вокруг объекта UserDefaults, чтобы сделать свойства доступными без необходимости вставлять строковые ключи повсюду в вашем проекте.

extension UserDefaults {    public enum Keys {        static let hasSeenAppIntroduction = "has_seen_app_introduction"    }    /// Indicates whether or not the user has seen the onboarding.    var hasSeenAppIntroduction: Bool {        set {            set(newValue, forKey: Keys.hasSeenAppIntroduction)        }        get {            return bool(forKey: Keys.hasSeenAppIntroduction)        }    }}

Он позволяет устанавливать и получать значения из пользовательских настроек по умолчанию из любого места следующим образом:

UserDefaults.standard.hasSeenAppIntroduction = trueguard !UserDefaults.standard.hasSeenAppIntroduction else { return }showAppIntroduction()

Это кажется отличным решением, но оно легко может превратиться в большой файл с множеством установленных ключей и свойств. Код повторяется и поэтому напрашивается способ сделать это проще. Пользовательская обертка свойств с использованием ключевого слова @propertyWrapper может помочь нам решить эту проблему.

Использование оберток свойств для удаления шаблонного кода

Взяв вышеприведенный пример, мы можем переписать код и убрать много лишнего. Для этого нам нужно создать новое свойство-обертку, которое мы назовем UserDefault. В конечном итоге это позволит нам определить его как свойство пользователя по умолчанию .

Если вы используете SwiftUI, возможно, вам лучше использовать обертку свойства AppStorage. Рассмотрим это просто как пример замены повторяющегося кода.

@propertyWrapperstruct UserDefault<Value> {    let key: String    let defaultValue: Value    var container: UserDefaults = .standard    var wrappedValue: Value {        get {            return container.object(forKey: key) as? Value ?? defaultValue        }        set {            container.set(newValue, forKey: key)        }    }}

Обертка позволяет передать значение по умолчанию, если еще нет зарегистрированного значения. Мы можем передать любое значение, поскольку обертка определяется общим значением Value.

Теперь мы можем изменить нашу предыдущую имплементацию кода и создать следующее расширение для типа UserDefaults:

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool}

Как видите, мы можем использовать сгенерированный по умолчанию инициализатор struct из обертки определяемого свойства. Мы передаем тот же ключ, что и раньше, и устанавливаем значение по умолчанию false. Использовать это новое свойство очень просто:

UserDefaults.hasSeenAppIntroduction = falseprint(UserDefaults.hasSeenAppIntroduction) // Prints: falseUserDefaults.hasSeenAppIntroduction = trueprint(UserDefaults.hasSeenAppIntroduction) // Prints: true

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

extension UserDefaults {    static let groupUserDefaults = UserDefaults(suiteName: "group.com.swiftlee.app")!    @UserDefault(key: "has_seen_app_introduction", defaultValue: false, container: .groupUserDefaults)    static var hasSeenAppIntroduction: Bool}

Добавление дополнительных свойств с помощью одной обертки

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

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool    @UserDefault(key: "username", defaultValue: "Antoine van der Lee")    static var username: String    @UserDefault(key: "year_of_birth", defaultValue: 1990)    static var yearOfBirth: Int}

Как вы можете видеть, обертка работает с любым типом, который вы определите, если этот тип поддерживается для сохранения в пользовательских настройках по умолчанию.

Хранение опционалов с помощью обертки свойств пользователя по умолчанию

Распространенная проблема, с которой можно столкнуться при использовании оберток свойств, заключается в том, что общее значение позволяет определить либо все опции, либо все значения без обертки. В сообществе существует распространенная техника решения этой проблемы, которая использует пользовательский протокол AnyOptional:

/// Allows to match for optionals with generics that are defined as non-optional.public protocol AnyOptional {    /// Returns `true` if `nil`, otherwise `false`.    var isNil: Bool { get }}extension Optional: AnyOptional {    public var isNil: Bool { self == nil }}

Мы можем расширить нашу обертку свойств UserDefault, чтобы она соответствовала этому протоколу:

extension UserDefault where Value: ExpressibleByNilLiteral {        /// Creates a new User Defaults property wrapper for the given key.    /// - Parameters:    ///   - key: The key to use with the user defaults store.    init(key: String, _ container: UserDefaults = .standard) {        self.init(key: key, defaultValue: nil, container: container)    }}

Это расширение создает дополнительный инициализатор, который устраняет требование определения значения по умолчанию и позволяет работать с опциями.

Наконец, нам нужно настроить наш установщик значений обертки, чтобы позволить удалять объекты из пользовательских значений по умолчанию:

@propertyWrapperstruct UserDefault<Value> {    let key: String    let defaultValue: Value    var container: UserDefaults = .standard    var wrappedValue: Value {        get {            return container.object(forKey: key) as? Value ?? defaultValue        }        set {            // Check whether we're dealing with an optional and remove the object if the new value is nil.            if let optional = newValue as? AnyOptional, optional.isNil {                container.removeObject(forKey: key)            } else {                container.set(newValue, forKey: key)            }        }    }    var projectedValue: Bool {        return true    }}

Теперь это позволяет нам определять опционалы и принимать значения равными нулю:

extension UserDefaults {    @UserDefault(key: "year_of_birth")    static var yearOfBirth: Int?}UserDefaults.yearOfBirth = 1990print(UserDefaults.yearOfBirth) // Prints: 1990UserDefaults.yearOfBirth = nilprint(UserDefaults.yearOfBirth) // Prints: nil

Отлично! Теперь мы можем справиться со всеми сценариями с помощью обертки пользовательских настроек по умолчанию. Последнее, что нужно добавить, это прогнозируемое значение, которое будет преобразовано в Combine publisher, как и в обертке свойства @Published.

Прогнозирование значения из обертки свойства

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

Чтобы сделать это с помощью обертки свойства user defaults, мы должны добавить publisher, который будет субъектом сквозной передачи. Все дело в названии: он будет просто передавать изменения значений. Реализация выглядит следующим образом:

import Combine  @propertyWrapper struct UserDefault<Value> {     let key: String     let defaultValue: Value     var container: UserDefaults = .standard     private let publisher = PassthroughSubject<Value, Never>()          var wrappedValue: Value {         get {             return container.object(forKey: key) as? Value ?? defaultValue         }         set {             // Check whether we're dealing with an optional and remove the object if the new value is nil.             if let optional = newValue as? AnyOptional, optional.isNil {                 container.removeObject(forKey: key)             } else {                 container.set(newValue, forKey: key)             }             publisher.send(newValue)         }     }     var projectedValue: AnyPublisher<Value, Never> {         return publisher.eraseToAnyPublisher()     } } We can now start 

Теперь мы можем начать наблюдать за изменениями в нашем объекте следующим образом:

let subscription = UserDefaults.$username.sink { username in     print("New username: \(username)") } UserDefaults.username = "Test" // Prints: New username: Test 

Это замечательно! Это позволяет нам реагировать на любые изменения. Поскольку до этого мы определяли наше свойство статически, теперь этот publisher будет работать во всем нашем приложении. Если вы хотите узнать больше о Combine, обязательно ознакомьтесь с моей статьей Начало работы с фреймворком Combine в Swift.

Определение образцов файлов с помощью обертки свойств

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

Возьмем следующую обертку свойств, в которой мы определяем файл-образец:

@propertyWrapperstruct SampleFile {    let fileName: String    var wrappedValue: URL {        let file = fileName.split(separator: ".").first!        let fileExtension = fileName.split(separator: ".").last!        let url = Bundle.main.url(forResource: String(file), withExtension: String(fileExtension))!        return url    }    var projectedValue: String {        return fileName    }}

Мы можем использовать эту обертку для определения файлов-образцов, которые могут понадобиться для отладки или при выполнении тестов:

struct SampleFiles {    @SampleFile(fileName: "sample-image.png")    static var image: URL}

Свойство projectedValue позволяет нам считывать имя файла, используемое в обертке свойства:

print(SampleFiles.image) // Prints: "../resources/sample-image.png"print(SampleFiles.$image) // Prints: "sample-image.png"

Это может быть полезно в тех случаях, когда вы хотите знать, какое начальное значение (значения) было использовано оберткой для вычисления конечного значения. Обратите внимание, что здесь мы используем знак доллара в качестве префикса для доступа к прогнозируемому значению.

Доступ к определенным приватным свойствам

Хотя не рекомендуется работать с обертками свойств таким образом, в некоторых случаях может оказаться полезным прочитать определенные свойства обертки. Я просто продемонстрирую, что это возможно, и вы, возможно, захотите переосмыслить свою реализацию кода, если у вас возникнет необходимость в доступе к приватным свойствам.

В приведенном выше примере мы можем получить доступ и к имени файла, используя префикс подчеркивания. Это позволяет нам получить доступ к приватному свойству filename:

extension SampleFiles {    static func printKey() {        print(_image.fileName)    }}

Отнеситесь к этому со скептицизмом и посмотрите, не можете ли вы решить свои задачи, используя другие варианты решения.

Другие примеры использования

Обертки свойств используются и в стандартных API Swift. Особенно в SwiftUI вы найдете такие обертки свойств, как @StateObject и @Binding. Все они имеют нечто общее: упрощение доступа к часто используемым шаблонам.

Вдохновившись этими встроенными примерами, вы можете начать думать о создании своих собственных оберток свойств. Другой идеей может быть создание обертки для выполнения действий в командной строке:

@Option(shorthand: "m", documentation: "Minimum value", defaultValue: 0)var minimum: Int

Или для представлений, макеты которых определены в коде:

final class MyViewController {    @UsesAutoLayout    var label = UILabel()}

Этот последний пример я часто использую в своих проектах для представлений, которые используют автоматическую компоновку и требуют, чтобы translatesAutoresizingMaskIntoConstraints был установлен в false. Подробнее об этом примере вы можете прочитать в моей статье в блоге: Автоматическая компоновка в Swift: Программное написание ограничений.

Заключение

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

Если вы хотите желаете узнать больше советов по Swift, загляните на страницу категории swift. Не стесняйтесь связаться со мной или написать мне в Twitter, если у вас есть дополнительные рекомендации или отзывы. Спасибо!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru