TypedDefaults 1.2.0

TypedDefaults 1.2.0

测试已检测
语言语言 SwiftSwift
许可证 MIT
发布上次发布2017年10月
SwiftSwift 版本4.0
SPM支持 SPM

Kazunobu Tasakakitoko552 维护。



TypedDefaults

TypedDefaults 是一个用于在类型安全的情况下使用 NSUserDefaults 的实用库。

动机

保持冷静并大量使用类型擦除 这篇由 Gwendolyn Weston 在 try! Swift 2016 上发表的演讲非常棒,它启发了我将 类型擦除 技巧应用在应用开发的具体案例中。

安装

  • 使用 CocoaPods 安装

    use_frameworks!
    
    platform :ios, '8.0'
    
    pod 'TypedDefaults'

要求

  • iOS 8.0+
  • Swift 2.2

功能

  • 自定义类型可以安全地存储在 NSUserDefaults 中
  • 支持依赖注入

用法

自定义类型

自定义类型可以安全地存储在 NSUserDefaults 中。
自定义类型只需要采用后面描述的 DefaultsConvertible 协议,无需继承 NSObject。
因此,Swift 原生 Class、Struct 和 Enum 都可用于自定义类型。(当然,也可用 NSObject 的子类。)

DefaultsConvertible 协议

public protocol DefaultConvertible {

    static var key: String { get }

    init?(_ object: AnyObject)

    func serialize() -> AnyObject
}

自定义类型作为 AnyObject 存储在 NSUserDefaults 中。
serialize() 在保存时被调用,而 init?(_ object:) 在从 NSUserDefaults 中获取时被调用。

假设每个自定义类型和应用程序中使用的每个配置是一对一的关系。
因此,key 作为类型属性准备,以便将 key 分配给一个 custom type

自定义类型的例子

这是一个以 保存照片到 CameraRoll 的标志照片大小 作为相机配置的自定义类型的例子。

struct CameraConfig: DefaultConvertible {
    enum Size: Int {
        case Large, Medium, Small
    }

    var saveToCameraRoll: Bool
    var size: Size

    // MARK: DefaultConvertible

    static let key = "CameraConfig"

    init?(_ object: AnyObject) {
        guard let dict = object as? [String: AnyObject] else {
          return nil
        }

        self.saveToCameraRoll = dict["cameraRoll"] as? Bool ?? true
        if let rawSize = dict["size"] as? Int,
         let size = Size(rawValue: rawSize) {
            self.size = size
         } else {
            self.size = .Medium
        }
    }

    func serialize() -> AnyObject {
        return ["cameraRoll": saveToCameraRoll, "size": size.rawValue]
    }
}

将自定义类型保存到NSUserDefaults

PersistentStore 是一个类,用于将自定义类型保存到 NSUserDefaults。
以下是使用示例。

/// Specify a custom type when initializing PersistentStore
let userDefaults = PersistentStore<CameraConfig>()

// Make an instance of CameraConfig
var cs = CameraConfig([:])!

// Set
userDefaults.set(cs)
// Get
userDefaults.get()?.size // Medium

/// Change the size
cs.size = .Large

// Set
userDefaults.set(cs)
// Get
userDefaults.get()?.size // Large

依赖注入支持

NSUserDefaults 不适合单元测试,因为它在文件系统中持续存储数据。
TypedDefaults 提供了 InMemoryStoreAnyStore 类型,用于依赖注入,以便测试基于存储在 NSUserDefaults 中的自定义类型的不同行为。

InMemoryStore 除了继承 PersistentStore,还采用了 DefaultStoreType 协议。
然而,InMemoryStore 仅保留自定义类型在内存中,与 PersistentStore 不同。
对于 AnyStore,它是对 PersistentStoreInMemoryStore 的抽象类型。

示例

以下示例展示了在单元测试中使用 InMemoryStoreAnyStore 而不是 PersistentStore

有一个名为 CameraViewController 的类,它继承自 UIViewController。
它有一个 config 属性,用于保留保存在 NSUserDefaults 中的自定义类型。为了支持依赖注入,将 config 的类型设置为 AnyStore

class CameraViewController: UIViewController {
    lazy var config: AnyStore<CameraConfig> = {
        let ds = PersistentStore<CameraConfig>()
        return AnyStore(ds)
    }()

    ...
}

因为 config 的类型不是 PersistentStoreAnyStore,所以可以在单元测试中将其替换为 InMemoryStore,如下所示。

class CameraViewControllerTests: XCTestCase {
    var viewController: CameraViewController!

    override func setUp() {
        viewController = CameraViewController()

        let defaultConfig = CameraConfig([:])!
        let ds = InMemoryStore<CameraConfig>()
        ds.set(defaultConfig) //
        viewController.config = AnyStore(ds)
    }
}

发行说明

https://github.com/tasanobu/TypedDefaults/releases

许可

TypedDefaults 使用 MIT 许可证发布。请参见 LICENSE 以获取详细信息。