Ale*_*lex 10 nsuserdefaults type-safety swift
关于Swift和Cocoa的问题之一就是使用NSUserDefaults,因为没有类型信息,并且总是需要将结果转换为objectForKey您期望获得的结果.这是不安全和不切实际的.我决定解决这个问题,让NSUserDefaults在Swift-land更加实用,并希望在此过程中学到一些东西.这是我一开始的目标:
清除语法,尽可能使用下标.例如,这将是完美的:
3.1.set:UserDefaults [.MyKey] = value
3.2.get:let value = UserDefaults [.MyKey]
通过自动[取消]归档来支持符合NSCoding协议的类
我开始创建这个通用结构:
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
Run Code Online (Sandbox Code Playgroud)
然后我创建了另一个结构,用作应用程序中所有键的容器:
struct UDKeys {}
Run Code Online (Sandbox Code Playgroud)
然后可以将其扩展为在需要的地方添加密钥:
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
Run Code Online (Sandbox Code Playgroud)
请注意每个键如何与其关联的类型.它表示要保存的信息的类型.此外,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
Run Code Online (Sandbox Code Playgroud)
我们的想法是创建一个特定域的实例,然后以这种方式访问每个方法:
let value = UserDefaults.myMethod(...)
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法,如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 }
}
}
Run Code Online (Sandbox Code Playgroud)
但后来我发现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 }
}
Run Code Online (Sandbox Code Playgroud)
这很好用:
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
Run Code Online (Sandbox Code Playgroud)
注意我怎么不能打电话UserDefaults.get(.MyKey).我必须使用UDKeys.MyKey.我不能这样做,因为它还不可能在通用结构上有静态变量!
接下来,让我们尝试5号.现在这很令人头疼,这就是我需要很多帮助的地方.
根据文档,属性列表类型是:
默认对象必须是属性列表,即(或集合的实例组合)的实例:NSData,NSString,NSNumber,NSDate,NSArray或NSDictionary.
在斯威夫特手段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
}
Run Code Online (Sandbox Code Playgroud)
但正如你会注意到的,虽然这很好:
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
Run Code Online (Sandbox Code Playgroud)
这不是:
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
Run Code Online (Sandbox Code Playgroud)
这也不是:
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
Run Code Online (Sandbox Code Playgroud)
甚至不是这样的:
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
Run Code Online (Sandbox Code Playgroud)
问题是它们都是有效的属性列表类型,但Swift显然将数组和整数解释为结构,而不是它们的Objective-C类对应物.然而:
func set <T: Any>(key: UDKey<T>, _ value: T)
Run Code Online (Sandbox Code Playgroud)
也不会工作,因为任何值类型,不仅仅是具有类似于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
}
Run Code Online (Sandbox Code Playgroud)
AnyObjectiveCObject 会接受任何快速类和快速数组,字典,数字(整数,浮点数,布尔值等转换为NSNumber),字符串......
不幸的是,AFAIK这个不存在.
题:
如何编写泛型函数(或重载泛型函数的集合),其泛型类型T可以是任何引用类型或 Swift可以在Objective-C中转换为引用类型的任何值类型?
解决:在我得到的答案的帮助下,我到达了我想要的东西.如果有人想看看我的解决方案,这里就是.
我不是故意吹嘘但是......哦,我在开玩笑,我完全做到了!
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")!)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1815 次 |
| 最近记录: |