Swift 中的 NSUserDefaults - 实现类型安全 [英] NSUserDefaults in Swift - implementing type safety

查看:50
本文介绍了Swift 中的 NSUserDefaults - 实现类型安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关于 Swift 和 Cocoa 的问题之一是使用 NSUserDefaults,因为没有类型信息并且总是需要将 objectForKey 的结果转换为您期望的结果得到.这是不安全和不切实际的.我决定解决这个问题,让 NSUserDefaults 在 Swift-land 中更加实用,并希望在此过程中学到一些东西.这是我一开始的目标:

One of the things that bugs me about Swift and Cocoa together is working with NSUserDefaults, because there is no type information and it is always necessary to cast the result of objectForKey to what you are expecting to get. It is unsafe and impractical. I decided to tackle this problem, making NSUserDefaults more practical in Swift-land, and hopefully learning something along the way. Here were my goals in the beginning:

  1. 完整的类型安全:每个键都有一种与之关联的类型.设置值时,只应接受该类型的值,获取值时,结果应具有正确的类型
  2. 含义和内容明确的全局键列表.该列表应该易于创建、修改和扩展
  3. 简洁的语法,尽可能使用下标.例如,这将变得完美:

  1. Complete type safety: each key has one type associated with it. When setting a value, only a value of that type should be accepted and when getting a value the result should come out with the correct type
  2. Global list of keys which are clear in meaning and content. The list should be easy to create, modify and extend
  3. Clean syntax, using subscripts if possible. For example, this would be perfect:

3.1.设置:UserDefaults[.MyKey] = value

3.1. set: UserDefaults[.MyKey] = value

3.2.获取:让值 = UserDefaults[.MyKey]

3.2. get: let value = UserDefaults[.MyKey]

支持符合 NSCoding 协议的类自动[取消]归档它们

Support for classes that conform to the NSCoding protocol by automatically [un]archiving them

我从创建这个通用结构开始:

I started by creating this generic struct:

struct UDKey <T>  {
    init(_ n: String) { name = n }
    let name: String
}

然后我创建了另一个结构体,作为应用程序中所有键的容器:

Then I created this other struct that serves as a container for all the keys in an application:

struct UDKeys {}

然后可以扩展以在需要的地方添加密钥:

This can then be extended to add keys wherever needed:

extension UDKeys {
    static let MyKey1 = UDKey<Int>("MyKey1")
    static let MyKey2 = UDKey<[String]>("MyKey2")
}

注意每个键是如何关联一个类型的.它表示要保存的信息的类型.此外,name 属性是用作 NSUserDefaults 键的字符串.

Note how each key has a type associated with it. It represents the type of the information to be saved. Also, the name property is the string that is to be used as a key for NSUserDefaults.

这些键可以全部列在一个常量文件中,也可以在每个文件的基础上使用扩展名添加,靠近它们用于存储数据的位置.

The keys can be listed all in one constants file, or added using extensions on a per-file basis close to where they are being used for storing data.

然后我创建了一个UserDefaults"类,负责处理信息的获取/设置:

Then I created an "UserDefaults" class responsible for handling the getting/setting of information:

class UserDefaultsClass {
    let storage = NSUserDefaults.standardUserDefaults()
    init(storage: NSUserDefaults) { self.storage = storage }
    init() {}

    // ...
}

let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation

这个想法是为特定域创建一个实例,然后以这种方式访问​​每个方法:

The idea is that one instance for a particular domain is created and then every method is accessed in this way:

let value = UserDefaults.myMethod(...)

我更喜欢这种方法,而不是像 UserDefaults.sharedInstance.myMethod(...)(太长!)或对所有事情使用类方法.此外,这允许通过使用多个具有不同存储值的 UserDefaultsClass 同时与多个域进行交互.

I prefer this approach to things like UserDefaults.sharedInstance.myMethod(...) (too long!) or using class methods for everything. Also, this allows interacting with various domains at the same time by using more than one UserDefaultsClass with different storage values.

到目前为止,第 1 条和第 2 条已经处理完毕,但现在困难的部分开始了:如何实际设计 UserDefaultsClass 上的方法以符合其余部分.

So far, items 1 and 2 have been taken care of, but now the difficult part is starting: how to actually design the methods on UserDefaultsClass in order to comply with the rest.

例如,让我们从第 4 项开始.首先我尝试了这个(这段代码在 UserDefaultsClass 中):

For example, let's start with item 4. First I tried this (this code is inside 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 项有一半……

But then I find out that Swift doesn't allow generic subscripts!! Alright, then I guess I'll have to use functions then. There goes half of item 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.我不能这样做,因为还不可能在通用结构上有静态变量!!

Note how I can't call UserDefaults.get(.MyKey). I have to use UDKeys.MyKey. And I can't do that because it's not yet possible to have static variables on a generic struct!!

接下来,让我们尝试第 5 项.现在这让我很头疼,而这正是我需要大量帮助的地方.

Next, let's try number 5. Now that has been an headache and that's where I need lots of help.

属性列表类型是,根据文档:

Property list types are, as per the docs:

一个默认对象必须是一个属性列表,即(或对于集合的实例组合): NSData, NSString,NSNumber、NSDate、NSArray 或 NSDictionary.

A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.

Swift 中的意思是 Int, [Int], [[String:Bool]], [[String:[Double]]] 等都是属性列表类型.起初我以为我可以写这个并相信使用这段代码的人记住只允许 plist 类型:

That in Swift means Int, [Int], [[String:Bool]], [[String:[Double]]], etc are all property list types. At first I thought that I could just write this and trust whoever is using this code to remember that only plist types are allowed:

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
}

但是你会注意到,虽然这很好用:

But as you'll notice, while this works fine:

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 类对应物.但是:

The problem is that they are all valid property list types yet Swift obviously interprets arrays and ints as structs, not as their Objective-C class counterparts. However:

func set <T: Any>(key: UDKey<T>, _ value: T)

也不会工作,因为这样任何值类型,而不仅仅是具有 Obj-C 提供的类表亲的值类型,都被接受,并且 storage.setObject(value, forKey: key.name) 不再有效,因为 value 必须是引用类型.

won't work either, because then any value type, not just the ones that have a class cousin courtesy of Obj-C, is accepted, and storage.setObject(value, forKey: key.name) is no longer valid because value has to be a reference type.

如果 Swift 中存在一个协议,它接受任何引用类型和任何可以转换为 Objective-c 中引用类型的值类型(比如 [Int] 和我提到的其他例子)这个问题就解决了:

If a protocol existed in Swift that accepted any reference type and any value type that can be converted to a reference type in objective-c (like [Int] and the other examples I mention) this problem would be solved:

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 将接受任何 swift 类和 swift 数组、字典、数字(转换为 NSNumber 的整数、浮点数、布尔值等)、字符串...

AnyObjectiveCObject would accept any swift classes and swift arrays, dictionaries, numbers (ints, floats, bools, etc that convert to NSNumber), strings...

不幸的是,AFAIK 这不存在.

Unfortunately, AFAIK this doesn't exist.

问题:

我如何编写泛型函数(或重载泛型函数的集合),其泛型类型 T 可以是任何引用类型 Swift 可以转换的任何 类型到 Objective-C 中的引用类型?

How can I have write a generic function (or collection of overloaded generic functions) whose generic type T can be any reference type or any value type that Swift can convert to a reference type in Objective-C?

已解决:在我得到的答案的帮助下,我得到了我想要的.如果有人想看看我的解决方案,这里.

Solved: With the help of the answers I got, I arrived at what I wanted. In case anyone wants to take a look at my solution, here it is.

推荐答案

我不是要吹嘘,但......哦,我在开玩笑,我完全可以

I don't mean to brag but ... oh who am I kidding, I totally do!

Preferences.set([NSData()], forKey: "MyKey1")
Preferences.get("MyKey1", type: type([NSData]))
Preferences.get("MyKey1") as [NSData]?

func crunch1(value: [NSData])
{
    println("Om nom 1!")
}

crunch1(Preferences.get("MyKey1")!)

Preferences.set(NSArray(object: NSData()), forKey: "MyKey2")
Preferences.get("MyKey2", type: type(NSArray))
Preferences.get("MyKey2") as NSArray?

func crunch2(value: NSArray)
{
    println("Om nom 2!")
}

crunch2(Preferences.get("MyKey2")!)

Preferences.set([[String:[Int]]](), forKey: "MyKey3")
Preferences.get("MyKey3", type: type([[String:[Int]]]))
Preferences.get("MyKey3") as [[String:[Int]]]?

func crunch3(value: [[String:[Int]]])
{
    println("Om nom 3!")
}

crunch3(Preferences.get("MyKey3")!)

这篇关于Swift 中的 NSUserDefaults - 实现类型安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆