Swift 进阶笔记-业务化Tips (3)-帅气的 UserDefaults

UserDefaults

UserDefaults 可以存储轻量级的本地客户端数据,适合用于记住用户登录状态、信息等。然而如何写出简单好用不失优雅的存取方式呢?本篇实现一个封装成静态变量的 UserDefaults。先放结果和 Demo:

// 取
dataLabel.text = CoolUserDefaults.data.value ?? "nil"
// 存
CoolUserDefaults.data.value = changeDataField.text

是否看起来既清爽又简单?

先上Demo
https://github.com/wiiale/AdvancedSwiftThinker/tree/master/T03-CoolUserDefaults

UserDefaults.gif

写时复制

写实复制指元素在一般状况下以只读方式共享,资源的复制只有在需要写入的时候才进行。实现写时复制能够在创建保留值语义结构体时保持像指针那样的高效操作。

使用 Swift 值类型进行写时复制:

import Foundation

struct User {
    var id = 1
}

final class Ref {
    var value: T
    init(_ value: T) {
        self.value = value
    }
}

struct Box {
    private var ref: Ref
    init(_ value: T) {
        ref = Ref(value)
    }
    
    var value: T {
        get { return ref.value }
        set {
            guard isKnownUniquelyReferenced(&ref) else {
                ref = Ref(newValue)
                return
            }
            ref.value = newValue
        }
    }
}

let user = User()
let box = Box(user)
var box2 = box     // box、box2共享 box.ref实例
box2.value.id = 2  // 创建新的box2 ref对象

由于 struct 是一个值类型,当我们将它赋值给另一个变量时,它的值被复制,而属性 ref 遗留的实例被两个副本共享,因为它是一个引用类型。

guard isKnownUniquelyReferenced(&ref) else {
    ref = Ref(newValue)
    return
}

这个方法中检查引用是否共享一个引用实例。

封装 UserDefaults

但是本篇文章的重点并不在于写时复制,而在于实现写实复制时传递的思想,封装起来也并不繁琐复杂。

final public class UserDefaultsBox {
    
    public var value: T {
        didSet {
            setterAction(value)
        }
    }
    
    public typealias SetterAction = (T) -> Void
    var setterAction: SetterAction
    
    public init(_ v: T, setterAction action: @escaping SetterAction) {
        value = v
        setterAction = action
    }
}

关联 UserDefaults 的存取功能封装 Box。

public var value: T {
    didSet {
        setterAction(value)
    }
}

value 的实现与写时复制在需要时复制一样,只有在需要被赋值时才进行 setter 操作。

实现需要存储的内容:

private let dataKey = "userData"
···

final public class CoolUserDefaults {
    
    static let defaults = UserDefaults(suiteName: "cool.user.defaults")!
    
    /// User Data
    public static var data: UserDefaultsBox = {
        let data = defaults.string(forKey: dataKey)
        
        return UserDefaultsBox(data) { data in
            defaults.set(data, forKey: dataKey)
        }
    }()

    ···
}

接着我们的静态变量是一个 Box 对象,将 defaults.set 设置为 set 方法。从而我们实现了以静态变量作为表现形式的 UserDefaults。

使用

// 取(查)
dataLabel.text = CoolUserDefaults.data.value ?? "nil"
// 存(增、改)
CoolUserDefaults.data.value = changeDataField.text
// 清除(删)
CoolUserDefaults.data.value = nil

批量清理

最后可以在自定义 CoolUserDefaults 中实现批量清缓存的方法,以便用于像退出登录一样需要清空用户数据的操作。

final public class CoolUserDefaults {

    ···

    public class func cleanAllUserDefaults() {
        
        do {
            data.value = nil
            // others ···
        }
        
        // reset suite
        
        let dict = defaults.dictionaryRepresentation()
        dict.keys.forEach({
            defaults.removeObject(forKey: $0)
        })
        defaults.synchronize()
        
        // reset standardUserDefaults
        
        let standardUserDefaults = UserDefaults.standard
        standardUserDefaults.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
        standardUserDefaults.synchronize()
    }
}

使用时直接调用CoolUserDefaults. cleanAllUserDefaults()即可。

参考资料:
Use Copy-On-Write With Swift Value Types
YepUserDefaults

文章Demo 汇总
https://github.com/wiiale/AdvancedSwiftThinker

本册文集中以“提出简单需求->简单实现需求片段”为流程,内容只针对该知识点实现的业务实例进行熟悉,业务也必定存在比文章方法更好的实现方式,文章旨在分析知识点并加深理解。文集不普及基本知识,不包含《Swift 进阶》书籍的详细内容。深入学习Swift请大家支持正版书籍(ObjC 中国)

你可能感兴趣的:(Swift 进阶笔记-业务化Tips (3)-帅气的 UserDefaults)