Skip to content
ApolloZhu edited this page Mar 24, 2021 · 12 revisions

学不动了.png(笑

@propertyWrapper 属性包装器

@EFStorageUserDefaults(forKey: "它的 key")
var 某个存在_UserDefaults_里的值: String = "当不存在时使用的默认值"

@dynamicMemberLookup 动态成员查找

let 用于读写的引用: EFStorageUserDefaultsRef<String> = UserDefaults.efStorage.某个_key

如果把上面那句完整地写出来的话,会是这样:

let 用于读写的引用 = EFStorageUserDefaultsRef<String>.forKey("某个_key")

这样就可以访问在 UserDefaults.standard 里那个 key 对应存储的内容了:

用于读写的引用.content

或者,如果你喜欢黑魔法的话,这样也可以:

UserDefaults.efStorage.某个_key as String?

如果可以推断类型的话,as String? 都可以不加。比如更改它的值就非常简单:

UserDefaults.efStorage.某个_key = "OwO"

放在其他什么地方吧

所有的 @propertyWrapper 类型都有很多可以配置的参数,如果你不想存在默认容器里也没关系

@EFStorageUserDefaults(forKey: "某个标识符",
                       in: 另外的UserDefaults, 
                       persistDefaultContent: true /*把默认值实际存储到容器里*/)
var 存在另外一个容器里的变量: Bool = 它的默认值

UserDefaults.另外的UserDefaults.efStorage.某个标识符 // 根据返回类型不同,可以是引用也可以是值

EFStorageUserDefaultsRef<Bool>.forKey("某个标识符", in: 另外的UserDefaults)

但是使用比如 App Group 的时候可能就需要全局替换了,不用担心,这个也能做到:

extension UserDefaults {
    private static let appGroup = UserDefaults(
        suiteName: kSecAttrAccessGroup as String
    )
    
    @_dynamicReplacement(for: makeDefault())
    class func makeDefaultForAppGroup() -> Self {
        return (UserDefaults.appGroup as? Self) ?? makeDefault()
    }
}

但是……

我们之前一直用 UserDefaults 在举例子,但其实我们还可以组合一下,同时存到多个地方:

@EFStorageComposition( // <- 组合类型
    EFStorageUserDefaults(wrappedValue: false, forKey: "isNewUser"),
    EFStorageKeychainAccess(wrappedValue: false, forKey: "isNewUser"))
var 新用户: Bool

有的时候呢,我们可能还需要支持一下之前版本标识符的名称,这也是完全没问题的:

@SomeEFStorage( // 可以使用 + 来结合多个 EFStorage 哦
    EFStorageKeychainAccess(wrappedValue: false, forKey: "paidBefore")
    + EFStorageUserDefaults(wrappedValue: false, forKey: "paidBefore")
    + EFStorageUserDefaults(wrappedValue: true, forKey: "oldHasPaidBeforeKey"))
var 给这个项目捐过款: Bool

但可能老版本里面存储的数据类型不一样,需要迁移呢?

@EFStorageComposition(
    EFStorageUserDefaults<String>(wrappedValue: "Nah",
                                  forKey: "理论上迁移的话key是一样的"),
    EFStorageMigrate( // <-- 这就是你要找的
        from: EFStorageUserDefaults<Int>( // <- 旧的容器
            wrappedValue: 1551,
            forKey: "但说实话key看情况一不一样无所谓",
            persistDefaultContent: true),
        by: { number in String(number) } // <- 迁移方式
    )
)
var 我好了: String

能放点其他的吗?太能了。

虽然 EFStorage 已经扩展了常见类型以便您直接使用,但还是有时候 需要扩展其他(您自己的)类型才能存储到某个 EFStorage 中。 具体怎么做可以参考 EFStorage 的源代码,这里只做简单介绍。

KeychainAccess

public protocol KeychainAccessStorable {
    func asKeychainAccessStorable() -> Result<AsIsKeychainAccessStorable, Error>
    static func fromKeychain(_ keychain: Keychain, forKey key: String) -> Self?
}

KeychainAccess 只能放 StringData 类型,所以您的类型必须转化成这两种中的一个:

public enum AsIsKeychainAccessStorable {
    case string(String)
    case data(Data)
}

UserDefaults

public protocol UserDefaultsStorable {
    func asUserDefaultsStorable() -> Result<AsIsUserDefaultsStorable, Error>
    static func fromUserDefaults(_ userDefaults: UserDefaults, forKey key: String) -> Self?
}

UserDefaults 虽然可以放 Any 类型,但是不支持直接存储的类型被直接放进去的话会炸, 所以除非你想清楚了,建议不要继承 AsIsUserDefaultsStorable 这个协议, 因为理论上所有应该实现这个协议的类型已经实现了这个协议(没有的话请开 PR,谢谢)。

public protocol AsIsUserDefaultsStorable: UserDefaultsStorable { }

YYCache

public protocol YYCacheStorable {
    func asYYCacheStorable() -> Result<NSCoding, Error>
    static func fromYYCache(_ yyCache: YYCache, forKey key: String) -> Self?
}

YYCache 因为是用 Objective-C 写的,所以只支持放 NSCoding

我的程序炸了

EFStorage 在要被存储的类型转换失败的时候会调用 assertionFailure,也就是生产环境中不会炸,但也什么都不会做。 你可以通过 @_dynamicReplacement(for:) 来替换掉 onConversionFailure/onStorageFailure, 自己处理这些错误。