欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Swift - Associated Object

程序员文章站 2024-02-20 15:37:52
...

不知道是从什么时候开始,**“是否能通过Category 给已有的类添加成员变量”**就成为了一道Objective-C 面试中的常见题目。不幸的消息是这个面试题目在Swift 中可能依旧会存在。

得益于Objective-C 的运行时(runtime)和键值编程(Key-Value Coding)的特性,我们可以在运行时向一个对象添加至存储。而在使用Category 扩展现有的类的功能的时候,直接添加实例变量这种行为是不被允许的,这时候一般就使用property 配合Associated Object 的方式,将一个对象“关联”到已有的要扩展的对象上。进行关联后,在对这个目标对象访问的时候,从外界看来,就似乎是直接通过属性访问对象的实例变量一样,可以说非常方便。

在Swift 中这样的方法依旧有效,只不过在写法上可能有些不同。两个对应的运行时的get 和set Associated ObjectAPI 是这样的:

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

这两个API 在Swift 中使用时,参数会自动变化,并且因为Swift 的安全性,在类型检查上严格了不少,因此我们有必要也进行一些调整。在Swift 中向某个extension 使用Associated Object 的方式将对象进行关联的写法是:

class MyClass {
    
}

private var key: Void?

extension MyClass {
    var title: String? {
        get {
            return objc_getAssociatedObject(self, &key) as? String
        }
        
        set {
            objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

func printTitle(input: MyClass) {
    if let title = input.title {
        print("Title \(title)")
    } else {
        print("没有设置")
    }
}
// 测试
let a = MyClass()
printTitle(input: a)
a.title = "biao ti"
printTitle(input: a)

// 输出
// 没有设置
// Title biao ti

key 的类型在这里声明为了Void?,通过& 操作符取地址并作为UnsafeRawPointer 类型被传入。这在Swift 与C 协作和指针操作时是一种很常见的用法。关于C 的指针操作和这些Unsafe 开头的类型的用法,可以参看UnsafePointer的内容

相关标签: iOS ios swift