Swift UserDefaults 构建一个成熟的存储&读取&删除本地缓存的工具类

510 阅读3分钟

我们经常会在开发时遇到这样的业务场景:

1.用户登录一次之后,再次登录app时,用户名作为默认登录账号。

2.当用户从某个页面退出时,假如这个页面有类似于轮播的组件,或者多视图选择器组件,我们希望用户登录回到APP后,仍然可以直接导航到该页面特定的轮播图或视图。

针对以上业务场景,我们需要存储用户的登录名,退出APP时当前视图的index到userDefaults中,然后在打开APP后,读取存储的key值,实现需求。

下面,我们来完整地构建一个,可扩展的、成熟的、开箱即用的userDefaults工具类。

Step1,我们来定义一个代表用户行为的类UserAction.swift

import Foundation

public enum UserAction: String {
    case firstLogin
    case indicatorIndex
    
    public var name: Stirng {
        rawValue
    }
}

这里,我们构建了一个枚举,它是String类型,并其中定义了一个name作为之后我们UserDefaultkey值。

Step2,我们来定义一个DataStore协议,遵循这个协议必须实现协议中的,读,写,删除方法。

import Foundation
import Combine

public protocol DataStore {
    func read<T: Decodable>(_ key: String) -> AnyPublisher<T?, Error>
    func write<T: Encodable>(_ key: String, value: T) -> AnyPublisher<Void, Error>
    func delete(_ key: String) -> AnyPublisher<Void, Error>
}

在这个DataStore.swift中,我们定义了三个方法,最终它们都会返回一个新的publisher

Step3,我们需要有一个UserDefaultsStore.swift的类,去遵循DataStore协议,并对协议中的方法进行具体的实现。

import Foundation
import Combine

public class UserDefaultsStore: DataStore {
    
    private let userDefaults: UserDefaults
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder
    
    init(
        userDefaults: UserDefaults,
        decoder: JSONDecoder,
        encoder: JSONEncoder
    ) {
        self.userDefaults = userDefaults
        self.decoder = decoder
        self.encoder = encoder
    }
    
    public func read<T: Decodable>(_ key: String) -> AnyPublisher<T?, Error> {
        Just(key)
            .tryMap { key -> T? in
                guard let data = userDefaults.data(forKey: key) else { return nil }
                return try decoder.decode(T.self, from: data)
            }.eraseToAnyPublisher()
    }
    
    public func write<T: Encodable>(_ key: String, value: T) -> AnyPublisher<Void, Error> {
        Just(())
            .tryMap { emptyValue -> Void in
                let data = try encoder.encode(value)
                userDefaults.set(data, forKey: key)
                return emptyValue
            }.eraseToAnyPublisher()
        
    }
    
    public func delete(_ key: String) -> AnyPublisher<Void, Error> {
        userDefaults.removeObject(forKey: key)
        return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
    }
    
}

Step4,我们构建一个标准用户行为管理类,在这个类中,我们将对本工具类进行最后的封装。

首先,我需要定义一个用户行为管理协议UserActionsManager

public protocol UserActionsManager {
    func writeToDefaults<T: Encodable>(_ userAction: UserAction, value: T) -> AnyPublisher<Void, Error>
    func readFromDefaults<T: Decodable>(_ userAction: UserAction) -> AnyPublisher<T?, Error>
    func removeFromDefaults(_ userAction: UserAction) -> AnyPublisher<Void, Error>
}

其次,我们来对UserDefaultStore进行扩展,我们用两个convenience init来分别实例化这个类,其中userId可以传进来用户的id,或者我们不区分用户的,用系统提供的.standard参数.

public extension UserDefaultsStore {
    convenience init?(
        userId: String,
        decoder: JSONDecoder = JSONDecoder(),
        encoder: JSONEncoder = JSONEncoder()
    ) {
        guard let customUserDefaults = UserDefaults(suiteName: userId) else { return nil }
        self.init(
            userDefaults: customUserDefaults,
            decoder: decoder,
            encoder: encoder
        )
    }
    
    convenience init(
        decoder: JSONDecoder = JSONDecoder(),
        encoder: JSONEncoder = JSONEncoder()
    ) {
        self.init(
            userDefaults: .standard,
            decoder: decoder,
            encoder: encoder
        )
    }
}

然后,我们的标准用户行为管理类StandardUserActionsManager遵循该协议.

import Foundation
import Combine

public protocol UserActionsManager {
    func writeToDefaults<T: Encodable>(_ userAction: UserAction, value: T) -> AnyPublisher<Void, Error>
    func readFromDefaults<T: Decodable>(_ userAction: UserAction) -> AnyPublisher<T?, Error>
    func removeFromDefaults(_ userAction: UserAction) -> AnyPublisher<Void, Error>
}

public class StandardUserActionsManager: UserActionsManager {
    
    private let userDefaultsStore: UserDefaultsStore
    private var cancellabes = Set<AnyCancellable>()
    
    init(
        userDefaultsStore: UserDefaultsStore
    ) {
        self.userDefaultsStore = userDefaultsStore
    }
    
    
    public func writeToDefaults<T>(_ userAction: UserAction, value: T) -> AnyPublisher<Void, Error> where T : Encodable {
        userDefaultsStore
            .write(userAction.name, value: value)
            .eraseToAnyPublisher()
    }
    
    public func readFromDefaults<T>(_ userAction: UserAction) -> AnyPublisher<T?, Error> where T : Decodable {
        userDefaultsStore
            .read(userAction.name)
            .eraseToAnyPublisher()
    }
    
    public func removeFromDefaults(_ userAction: UserAction) -> AnyPublisher<Void, Error> {
        userDefaultsStore
            .delete(userAction.name)
            .eraseToAnyPublisher()
    }    
}

public extension UserDefaultsStore {
    convenience init?(
        userId: String,
        decoder: JSONDecoder = JSONDecoder(),
        encoder: JSONEncoder = JSONEncoder()
    ) {
        guard let customUserDefaults = UserDefaults(suiteName: userId) else { return nil }
        self.init(
            userDefaults: customUserDefaults,
            decoder: decoder,
            encoder: encoder
        )
    }
    
    convenience init(
        decoder: JSONDecoder = JSONDecoder(),
        encoder: JSONEncoder = JSONEncoder()
    ) {
        self.init(
            userDefaults: .standard,
            decoder: decoder,
            encoder: encoder
        )
    }
}

这样,我们的标准用户行为管理工具类就完成了,当我们在业务中引入存储读取的功能时,直接实例化该类去实现需求。

最后,让我们写个小demo测试一下:

这里点击=号后,会显示一个计算结果,当我们不存储读取defaults时,当我们退出app,结果显然会清空,当我们完成存储读取后,打开app后仍然显示计算结果。

testUserDefaults.gif