基于Swift 5.1的 Keypath dynamicMemberLookup 新特性进行了MMKV的封装,使用起来会很方便。
使用方式:
extension KeyStore {
var name: Key<String> { .init(key: "name", defaultValue: "Tom") }
}
KVM.name = "Tom"
print(KVM.name) // "Tom"
主要代码:
//
// Default.swift
// Utils
//
// Created by Vincent on 2021/2/23.
//
import Foundation
import MMKV
public var KVM: KeyValueManager { KeyValueManager.default }
public typealias KeyStore = KeyValueManager.KeyStore
@dynamicMemberLookup
public class KeyValueManager: NSObject {
public typealias Key = KeyStore.Key
public struct KeyStore {
public struct Key<T> {
public let valueType: T.Type = T.self
public let key: String
public let defaultValue: T?
public init(key: String, defaultValue: T?) {
self.key = key
self.defaultValue = defaultValue
}
}
}
let store: KeyStore = .init()
/// mmapID 也是文件夹名称
let mmapID: String = {
let key = KeychainAccessUtil.MMKVPasswordKey
if let randomPath = try? KeychainAccessUtil.default.appKeychain?.getString(key) {
return randomPath
}else {
let randomPath = String.random(ofLength: 32)
try? KeychainAccessUtil.default.appKeychain?.set(randomPath, key: key)
return randomPath
}
}()
/// cryptKey 加密用密钥
let cryptKey: Data? = {
let key = KeychainAccessUtil.DBPasswordKey
if let password = try? KeychainAccessUtil.default.appKeychain?.getData(key) {
return password
}else {
let password = String.random(ofLength: 32).data(using: .utf8) ?? Data()
try? KeychainAccessUtil.default.appKeychain?.set(password, key: key)
return password
}
}()
public static let `default` = KeyValueManager()
private override init(){
super.init()
if var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
url.appendPathComponent("." + mmapID)
MMKV.initialize(rootDir: url.path)
}else {
assertionFailure("获取文件路径失败")
}
MMKV.register(self)
MMKV.enableAutoCleanUp(maxIdleMinutes: 2)
}
var defaultKV: MMKV? { MMKV.defaultMMKV(withCryptKey: cryptKey) }
}
// MARK: - Subscripts
extension KeyValueManager {
// String
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<String>>) -> String? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.string(forKey: key.key, defaultValue: key.defaultValue)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Int64
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Int64>>) -> Int64? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.int64(forKey: key.key, defaultValue: key.defaultValue ?? Int64.min)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// UInt64
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<UInt64>>) -> UInt64? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.uint64(forKey: key.key, defaultValue: key.defaultValue ?? UInt64.min)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Bool
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Bool>>) -> Bool? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.bool(forKey: key.key, defaultValue: key.defaultValue ?? false)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Float
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Float>>) -> Float? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.float(forKey: key.key, defaultValue: key.defaultValue ?? Float.nan)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Double
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Double>>) -> Double? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.double(forKey: key.key, defaultValue: key.defaultValue ?? Double.nan)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Date
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Date>>) -> Date? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.date(forKey: key.key, defaultValue: key.defaultValue)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Data
public subscript(dynamicMember keyPath: KeyPath<KeyStore, Key<Data>>) -> Data? {
get {
let key = store[keyPath: keyPath]
return self.defaultKV?.data(forKey: key.key, defaultValue: key.defaultValue)
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
// Object
public subscript<T: NSCoding & NSObjectProtocol>(dynamicMember keyPath: KeyPath<KeyStore, Key<T>>) -> T? {
get {
let key = store[keyPath: keyPath]
self.defaultKV?.object(of: T.self, forKey: key.key)
return self.defaultKV?.object(of: T.self, forKey: key.key) as? T
}
set {
let key = store[keyPath: keyPath]
if let value = newValue {
self.defaultKV?.set(value, forKey: key.key)
}else {
self.defaultKV?.removeValue(forKey: key.key)
}
}
}
}
// MARK: - MMKVHandler
extension KeyValueManager: MMKVHandler {
public func onMMKVCRCCheckFail(_ mmapID: String!) -> MMKVRecoverStrategic {
return .onErrorRecover
}
public func onMMKVFileLengthError(_ mmapID: String!) -> MMKVRecoverStrategic {
return .onErrorRecover
}
public func mmkvLog(with level: MMKVLogLevel, file: UnsafePointer<Int8>!, line: Int32, func funcname: UnsafePointer<Int8>!, message: String!) {
print(level.logType, tag: "MMKV", message, "", filePath: String.init(cString: file), funcName: String.init(cString: funcname), lineNum: Int(line))
}
}
// MARK: MMKVLogLevel
extension MMKVLogLevel {
// Cover to XloggerType
var logType: XloggerType {
switch self {
case .debug:
return .debug
case .info:
return .info
case .warning:
return .warning
case .error:
return .error
case .none:
return .none
@unknown default:
return .verbose
}
}
}
// MARK: - KeychainAccessUtil
extension KeychainAccessUtil {
public static var MMKVKey: String { "com.mmkv.www" }
public static var MMKVPasswordKey: String { "com.mmkv.password.www" }
}
代码中mmkvLog回调方法的内容可以自定义实现,我使用了封装后的xlog,需要自己替换
MMKV的加密密钥以及文件存储位置和mmapID都是存在Keychain中的,可以自己替换或者写死。
网友评论