属性存取数据:使用 runtime, 让 UserDefaults 飞

该博客介绍了如何通过Swift运行时来封装UserDefaults,避免字符串硬编码,提供统一的属性存取方式。作者创建了一个名为RCUserDefaults的类,并在扩展中添加属性,利用@NSManaged注解实现持久化。通过运行时获取类的属性列表,为每个属性动态添加对应类型的getter和setter方法。最后,通过单例方式提供 UserDefaults.std 访问入口。整个过程涉及Objective-C runtime的知识,如class_copyPropertyList、property_copyAttributeList等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

封装 UserDefaults,用属性的方式存取数据

  • 告别字符串硬编码,

  • 统一形式,不再有各种类型存取方法

double(forKey: _ )

integer(forKey: _)

主要有参考 woshiccm/RCUserDefaults

调用

        print(UserDefaults.std.name)
        UserDefaults.std.name = "two"
        print(UserDefaults.std.name)
配置

添加属性

extension RCUserDefaults{
    @NSManaged var name: String
}

原理

  • 一般给类添加属性,通过 objc_getAssociatedObject

添加的属性,放在内存中

  • 这里添加的属性,通过持久化的方式,放在磁盘中,

这时候直接添加存取方法,就好。

类似 Core Data , 需引入 @NSManaged

流程
  • 为了方便扩展,添加的属性放在扩展中

  • 通过 class_copyPropertyList, 拿到添加的属性

  • 给每一个属性,实现对应类型的存取方法,就好了

添加方法,使用 class_addMethod

添加的方法,自然是 double(forKey: _)set(_ , forKey: _ )

  • 为了知道添加属性,对应的类型。使用 property_copyAttributeList

实现

添加的属性,放在扩展中

做事情的是这个类 RCUserDefaults

为了简化写法,里面有一个属性是 UserDefaults.standard

UserDefaults.standard 负责,实际的存取

class RCUserDefaults: NSObject{
    private let userDefaults = UserDefaults.standard
}

添加扩展属性

extension RCUserDefaults{
    @NSManaged var name: String
}
如果没有 @NSManaged, 报错

Extensions must not contain stored properties

扩展,不能包含属性

与 Core Data 类似,添加 @NSManaged ,就好了

the @NSManaged attribute informs the Swift compiler that the storage and implementation of a property will be provided at runtime.

添加 @NSManaged,编译时不再检查,运行时保证存取即可

拿到添加的属性

常规的运行时套路, class_copyPropertyList

extension NSObject {

    static var properties: [Property] {
        var count: UInt32 = 0
        guard let propertyList = class_copyPropertyList(self, &count) else { return [] }

        var properties = [Property]()
        let cnt = Int(count)
        for i in 0..<cnt{
            properties.append(Property(x: propertyList[i]))
        }
        free(propertyList)
        return properties
    }
}
知道,添加属性对应的类型

才能在运行时,添加对应的 IMP,

double(forKey: _ )

还是


integer(forKey: _)

调用入口


 properties.append(Property(x: propertyList[i]))

代码:

拿到 objc_property_t, 取各种属性 property_copyAttributeList

struct Property {

    let name: String

    private(set) var typeEncoding = ObjCTypeEncoding.unknown("?")

    private(set) var type: NSObject.Type?

    init(x property: objc_property_t) {
        // 拿到,我们赋予的属性名
        name = String(cString: property_getName(property))

        var count: UInt32 = 0
        let attributeList = property_copyAttributeList(property, &count)!
        
        for i in 0..<Int(count) {
            let attribute = attributeList[i]

            let nick = String(cString: attribute.name)
            let value = String(cString: attribute.value)
            
            switch nick {
            case "T":
                var value = value
                if value.hasPrefix("@\"") && value.hasSuffix("\"") { // id
                    value = value
                        .replacingOccurrences(of: "@\"", with: "")
                        .replacingOccurrences(of: "\"", with: "")
                }
                else if value.hasPrefix("r") { // const
                    value = value.replacingOccurrences(of: "r", with: "")
                }
                // 判断是类,还是值类型
                if value.classExists{
                    //  string
                    //  stringOptional
                    //  data
                    typeEncoding = ObjCTypeEncoding(e: "@")
                    type = value.getClass
                }
                else {
                    //  bool
                    //  double
                    typeEncoding = ObjCTypeEncoding(e: value)
                }
            // ...
            default: break
            }
        }
        // ...
    }
}
最后一步,给每一个属性,实现对应类型的存取方法

又来到了这个类 RCUserDefaults


class RCUserDefaults: NSObject{
    // 单例
    fileprivate static let standard = RCUserDefaults()
    //  记录信息
    //  getter 方法签名字符串:  属性
    //  setter 方法签名字符串:  属性
    private static var mapping = [String: Property]()

    // 初始化方法,比较别致,
    // 参数,就是走一个形式
    // 为了这个单例, static let standard = RCUserDefaults()
    // 调用运行时添加 getter / setter 方法,  exchangeAccessMethods()
    public init(placeHolder nan: Bool? = nil){
        super.init()

        exchangeAccessMethods()
    }
}

运行时添加 getter / setter 方法


private func exchangeAccessMethods(){
        let properties = RCUserDefaults.properties
        //  处理每一个属性
        for property in properties{

            let getterKey = property.name
            let setterKey = objCDefaultSetterName(for: property.name)
            //  记录信息
            //  getter 方法签名字符串:  属性
            RCUserDefaults.mapping[getterKey] = property
            //  记录信息
            //  setter 方法签名字符串:  属性
            RCUserDefaults.mapping[setterKey] = property

            let getterSel : Selector = NSSelectorFromString(getterKey)
            let setterSel : Selector = NSSelectorFromString(setterKey)

            var getterImp: IMP!
            var setterImp: IMP!
            switch property.typeEncoding {
            
            case .int, .longLong, .uInt8:
                getterImp = unsafeBitCast(RCUserDefaults.longGetter, to: IMP.self)
                setterImp = unsafeBitCast(RCUserDefaults.longSetter, to: IMP.self)
            // ...
            // 处理其他类型
            }

            let setterTypes = "v@:\(property.typeEncoding)"
            let getterTypes = "\(property.typeEncoding)@:"

            setterTypes.withCString { typesCString in
                _ = class_addMethod(classForCoder, setterSel, setterImp, typesCString)
            }

            getterTypes.withCString { typesCString in
                _ = class_addMethod(classForCoder, getterSel, getterImp, typesCString)
            }
        }
    }

最后 IMP

其中一种类型的 gettersetter,

下面是 long ,长整型

     private static let longGetter: @convention(c) (RCUserDefaults, Selector) -> CLong = { _userDefault, _cmd in
        // 从 getter 方法,拿到我们赋予的属性名
        let key = defaultKey(for: _cmd)
        // 又看到了熟悉的取方法
        return _userDefault.userDefaults.integer(forKey: key)
    }

    private static let longSetter: @convention(c) (RCUserDefaults, Selector, CLong) -> Void = { _userDefault, _cmd, value in
        // 从 setter 方法,拿到我们赋予的属性名
        let key = defaultKey(for: _cmd)
        // 又看到了熟悉的存方法
        _userDefault.userDefaults.set(value, forKey: key)
    }
回到开头,添加命名空间

UserDefaults 添加计算属性,

返回的是 RCUserDefaults 的单例

extension UserDefaults{
    static var std: RCUserDefaults{
        return RCUserDefaults.standard
    }
}

github repo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值