jjzjj

swift - Swift 中的 NSUserDefaults - 实现类型安全

coder 2023-07-16 原文

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

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

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

    3.2.获取:让值 = UserDefaults[.MyKey]
  • 支持符合 NSCoding 协议(protocol)的类
    自动 [un] 归档它们
  • 支持 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 项。现在这让我很头疼,而这正是我需要大量帮助的地方。

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

    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 类型:
    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)不再有效,因为 value 必须是引用类型。

    如果 Swift 中存在接受任何引用类型和任何可以转换为 Objective-c 中的引用类型的值类型的协议(protocol)(如 [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将接受任何 swift 类和 swift 数组、字典、数字(转换为 NSNumber 的整数、浮点数、 bool 值等)、字符串...

    不幸的是,AFAIK 这不存在。

    问题:

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

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

    最佳答案

    我不是要吹牛,但......哦,我在开玩笑,我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 - Swift 中的 NSUserDefaults - 实现类型安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28136015/

    有关swift - Swift 中的 NSUserDefaults - 实现类型安全的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

      刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

    8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

      我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

    9. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

      我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

    10. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

      我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

    随机推荐