UserDefaults. Настройки пользователя

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

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

Интерфейс хранилища

При работе с UserDefaults разработчика чаще всего интересуют три метода:

func set(_ value: Any?, forKey defaultName: String)
func removeObject(forKey defaultName: String)
func object(forKey defaultName: String) -> Any?

Поскольку этот компонент был представлен широкой публике еще с выходом iOS 2.0, интерфейс с полной уверенностью можно назвать устаревшим.

  1. Для вызова в качестве ключа нужно передавать строку.

  2. Работа происходит с объектом типа Any, а не с конкретным типом.

  3. Работа происходит с ограниченным списком типов. Даже перечисления и OptionSet вызывают crash. (Сразу сделаем оговорку, что Codable объекты в UserDefaults хранить не стоит).

Ниже предлагается решение всех этих недостатков в виде объекта UserDefaultsContainer со следующим интерфейсом:

public init(keyedBy: K.Type, userDefaults: UserDefaults = .standard)
public subscript<T>(key: K) -> T? { get set }
public subscript<T>(key: K) -> T? where T : RawRepresentable { get set }
import Foundation

public final class UserDefaultsContainer<K: CodingKey> {
	private let userDefaults: UserDefaults
	
	public init(keyedBy: K.Type, userDefaults: UserDefaults = .standard) {
		self.userDefaults = userDefaults
	}
	
	public subscript<T>(key: K) -> T? {
		get { getValue(forKey: key.stringValue) as? T }
		set { set(value: newValue, forKey: key.stringValue) }
	}
	
	public subscript<T>(key: K) -> T? where T: RawRepresentable {
		get {
			if let rawValue = getValue(forKey: key.stringValue) as? T.RawValue {
				return T(rawValue: rawValue)
			} else {
				return nil
			}
		}
		set {
			set(value: newValue?.rawValue, forKey: key.stringValue)
		}
	}
	
	private func set(value: Any?, forKey key: String) {
		if let value {
			userDefaults.set(value, forKey: key)
		} else {
			userDefaults.removeObject(forKey: key)
		}
	}
	
	private func getValue(forKey key: String) -> Any? {
		return userDefaults.object(forKey: key)
	}
}

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

Самое время определить требования для работы с хранилищем:

  1. Объект может быть опциональным, значение которого может отсутствовать.

  2. Объект может быть не опциональным, имеющим значение по умолчанию.

  3. Объект может быть перечислением или OptionSet.

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

struct UserSettings {
	
	// MARK: - Types
	
	private enum SettingsKey: CodingKey {
		case age
		case name
		case planet
	}
	
	// MARK: - Properties
	
	private let storage = UserDefaultsContainer(keyedBy: SettingsKey.self)
	
	var age: Int? {
		get { storage[.age] }
		set { storage[.age] = newValue }
	}
	
	var name: String? {
		get { storage[.name] }
		set { storage[.name] = newValue }
	}
	
	var planet: Planet {
		get { storage[.planet] ?? .earth }
		set { storage[.planet] = newValue }
	}
}

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

var settings = UserSettings()
settings.age = 20
settings.planet = .mars

Причем не имеет значение, будет ли это singleton, environmentObject или объект, создаваемый на каждом экране. Выше рассмотрен последний вариант.

Заключение

В последнее время, с момента появления SwiftUI, все большей популярностью пользуются @propertyWrapper. Однако использование таких объектов накладывает дополнительные ограничения. К примеру, либо всегда нужно задавать значение по-умолчанию, либо все настройки будут опциональными. Более того, для того, чтобы работать с перечислениями и OptionSet, потребуется второй@propertyWrapper.

В самом разгаре WWDC 2022. К сожалению, Apple не предложили собственное решение для работы с устаревшим интерфейсом, несмотря на то, что сохранение настроек пользователя без сетевых вызовов - очень частая задача. Но это вовсе не означает, что нужно пользоваться тем, что есть, и пример тому был описан в этой статье.

  1. Property wrappers in Swift

  2. Create the Perfect UserDefaults Wrapper Using Property Wrapper

  3. A better approach to writing a UserDefaults Property Wrapper

  4. UserDefaults

  5. WWDC 2022

Источник: https://habr.com/ru/post/670378/


Интересные статьи

Интересные статьи

В Китае жизнь превратилась в компьютерную игру. Или даже в эпизод «Черного зеркала». Там с 01.01.2021 принят новый гражданский кодекс, официально узаконивший «Систему социального кредит...
Компания «Деловой разговор» — Титановый партнер 3СХ — осуществила расширенную интеграцию IP-АТС 3CX с Битрикс 24. Ранее уже существовали отдельные модули, решающие конкретные задачи, напр...
В этой статье расскажу, как работать через API с NSX Edge. Это решение от VMware выполняет для виртуального дата-центра функции маршрутизации, Firewall, NAT, DHCP, VPN и ...
Выгрузка пользователей из 1C ЗУП в Битрикс24 или правдивая история о том как настроить интеграцию 1С-Битрикс24 с ЗУП без 1С-ника В жизни так бывает, причём бывает чаще чем хотелось б...
Взял за правило всегда ставить все обновления ОС, возможно с небольшой задержкой (до недели), также поставил специальное приложение (APP Center) от производителя материнской платы, которое следит...