Одна из вещей, которая меня беспокоит о Swift и Cocoa вместе, работает с NSUserDefaults, потому что нет информации о типе, и всегда необходимо отображать результат objectForKey
на то, что вы ожидаете получить. Это небезопасно и непрактично. Я решил решить эту проблему, сделав NSUserDefaults более практичным в Swift-land и, надеюсь, чему-то научиться на этом пути. Вот мои цели вначале:NSUserDefaults in Swift - реализация типа безопасности
- Безопасность полного типа: каждая клавиша имеет один тип, связанный с ним. При настройке значения должно приниматься только значение этого типа, и при получении значения результат должен выдаваться с правильным типом
- Глобальный список ключей, которые понятны по смыслу и содержанию. Список должен быть простым в создании, изменении и расширении.
Чистый синтаксис, используя, по возможности, нижние индексы. Например, это будет будет идеальным:
3.1. set: UserDefaults [.MyKey] = значение
3.2. получим: пусть значение = UserDefaults [.MyKey]
Поддержка классов, которые соответствуют протоколу NSCoding по автоматически [не] архивирования их
- Поддержка всех типов списков недвижимости, принятых NSUserDefaults
Я начал создавать эту родовую-структуру:
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
Затем я создал эту другую структуру, которая служит в качестве контейнера для всех ключей в приложении заревым:
struct UDKeys {}
Это может быть расширена, чтобы добавить ключи везде, где необходимо:
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
Обратите внимание, как каждая клавиша имеет тип, связанный с ним. Он представляет тип информации для сохранения. Кроме того, свойство name является строкой, которая должна использоваться как ключ для NSUserDefaults.
Ключи могут быть перечислены все в одном файле констант или добавлены с использованием расширений для каждого файла в непосредственной близости от того места, где они используются для хранения данных.
Затем я создал класс «UserDefaults», ответственный за обработку с получением/установка информации:
class UserDefaultsClass {
let storage = NSUserDefaults.standardUserDefaults()
init(storage: NSUserDefaults) { self.storage = storage }
init() {}
// ...
}
let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation
Идея заключается в том, что один экземпляр для конкретного домена создается, а затем каждый метод доступен, таким образом, :
let value = UserDefaults.myMethod(...)
Я предпочитаю этот подход к вещам, как UserDefaults.sharedInstance.myMethod (...) (слишком долго!) или с помощью методов класса для всего. Кроме того, это позволяет одновременно взаимодействовать с различными доменами, используя более одного UserDefaultsClass с различными значениями хранения.
До сих пор детали 1 и 2 были учтены, но теперь начинается сложная часть: как на самом деле разрабатывать методы на UserDefaultsClass, чтобы соответствовать остальным.
Например, давайте начнем с пунктом 4. Сначала я попробовал это (этот код находится внутри UserDefaultsClass):
subscript<T: NSCoding>(key: UDKey<T>) -> T? {
set { storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(newValue), forKey: key.name) }
get {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
}
Но потом я узнаю, что Swift не позволяет общие индексы !! Хорошо, тогда я предполагаю, что мне придется использовать функции. Там идет половина пункта 3 ...
func set <T: NSCoding>(key: UDKey<T>, _ value: T) {
storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(value), forKey: key.name)
}
func get <T: NSCoding>(key: UDKey<T>) -> T? {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
И это работает просто отлично:
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
Обратите внимание, как я не могу назвать UserDefaults.get(.MyKey)
. Я должен использовать UDKeys.MyKey
. И я не могу этого сделать, потому что еще нет возможности иметь статические переменные в общей структуре!
Далее, давайте попробуем номер 5. Теперь это была головная боль, и именно здесь мне нужна большая помощь.
типы списков недвижимости являются, в соответствии с Документами:
объектпо умолчанию должен быть список свойств, то есть экземпляр (или коллекций сочетание экземпляров): NSData, NSString , NSNumber, NSDate, NSArray или NSDictionary.
То, что в Swift означает Int
, [Int]
, [[String:Bool]]
, [[String:[Double]]]
, и т.д., все типы списков свойств. Сначала я думал, что я мог бы просто написать это и доверие, кто использует этот код, чтобы помнить, что только Plist типов допускаются:
func set <T: AnyObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
Но, как вы заметите, в то время как это работает отлично:
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
Это не:
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
И это не как:
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
Даже не так:
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
Проблема заключается в том, что все они являются типами списков недвижимости пока Swift явно интерпретирует массивы и Интс, как структуры, а не как их аналогов класса Objective-C. Однако:
func set <T: Any>(key: UDKey<T>, _ value: T)
не будет работать, потому что тогда любой тип значения, а не только те, которые имеют класс кузен любезно Obj-C, принимается, и storage.setObject(value, forKey: key.name)
больше не действует, так как значение должно быть ссылочный тип.
Если протокол существовал в Swift, который принят любой тип опорного и значения любого типа, которые могут быть преобразованы в ссылочный тип в Objective-C (как [Int]
и другие примеры, которые я упомянуть) эта проблема будет решена:
func set <T: AnyObjectiveCObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObjectiveCObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
AnyObjectiveCObject
будет принимать какие-либо быстрые классы и быстрые массивы, словари, цифры (Интс, поплавки, Bools, и т.д., которые преобразовывают в NSNumber), струнные ...
к сожалению, AFAIK это не существует.
Вопрос:
Как я могу написать общую функцию (или коллекцию перегруженных родовых функций), общий тип T может быть любой ссылочный тип или любое значение тип, который Свифт может преобразовать ссылочный тип в Objective-C?
Решено: С помощью ответов я получил, я приехал на то, что я хотел. В случае, если кто-то хочет взглянуть на мое решение, это here.
Я не думаю, что это имеет смысл. Пользовательские настройки по умолчанию предназначены для (несколько более удобного) способа хранения параметров вызова команд. Речь идет не о сохранении полных объектов. –
@ThomasKilian В большинстве моих проектов я в конечном итоге сохраняю структуры данных, такие как '[[String: [Int]]]' и пользовательские объекты, такие как 'TimerInfo',' Person', 'Event', подобные вещи. Объекты не очень сложны и нет оснований для использования более сложных библиотек, таких как CoreData. Я не понимаю, почему это не имеет смысла. Кроме того, это отличная возможность для изучения. – Alex
Я обычно заканчиваю сериализацию для тех объектов, которые сводят его к минимальному количеству строк/ints/floats. Конечно, YMMV. Но, поскольку вы не разделяете пользовательские значения по умолчанию, я думаю, что безопасность типа имеет меньшее значение.Я бы просто создал класс оболочки вокруг него, чтобы убедиться, что вы получаете то, что хотите. –