但事实上,runtime有相当一部分内容很简单、很好用,比如今天要讲的关联。
在<objc/runtime.h>中,有三个和它有关的方法(是的,一共就三个):
objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects
顾名思义,三个分别是设置关联、获取关联、移除关联。下面我们就分别来说一下。
1. objc_setAssociatedObject
来看一下函数原型:void objc_setAssociatedObject(id object,constvoid *key,id value,objc_AssociationPolicy policy)
它没有返回值,共有四个参数:object 是关联的源对象,key 是关联的关键字,value 是关联的值,policy 是关联策略。
需要注意的是,key 是一个 const 的常量,而且每一个关联的 key 必须是唯一的。
关联策略 objc_AssociationPolicy 是一个枚举类型,一共有五种:
意思很好懂,看名字就知道了,就不多说了。
2. objc_getAssociatedObject
来看一下函数原型:id objc_getAssociatedObject(id object,constvoid *key)
返回类型为 id,返回的就是关联的那个值。有两个参数,和上面的一样,object 是关联的源对象,key 是关联的关键字。
注意,这个 key 必须和刚才的 key 一样才能获取到正确的值。
3. objc_removeAssociatedObject
来看一下函数原型:void objc_removeAssociatedObjects(id object)
它没有返回值,只有一个参数 object,是关联的源对象。意思就是移除 object 的所有关联。
注意,是移除所有关联。这个函数的本意是使一个对象回到“原始状态”,通常不会用这个方法,因为它会把你在别的地方加入的关联也一起移除。那怎么办呢?
还记得怎么设置关联吗?void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
第三个参数 value 传 nil 就是移除关联了,这只会移除指定 key 的关联而不是移除所有关联。
下面我们举个栗子,为了体现出关联的强大,我们在MRC下实践:
我们先定义一个 key。
然后创建一个 array,它就是关联的源对象。
然后我们创建一个字符串,它就是要关联到源对象上的那个值。
然后我们分三步来说:
1. 设置关联。输出结果为 “1 - associatedObject: I'm associatedObject.”
这说明我们设置关联成功了,我们通过overviewKey这个关键字就在array处取到了关联对象。
2. release 掉 overview(关联值)。输出结果为 “2 - associatedObject: I'm associatedObject.”
咦?!为什么我们还能获取到关联对象呢?
这正是关联的优点之一。它可以保证关联对象在整个生命周期中都可用。即使关联值(overview)被 release 掉了,我们还能使用关联对象。
3. 移除关联,设置关联值为nil。输出结果为 “3 - associatedObject: (null)“
此时关联对象才为空。
那么,什么时候我们会用到关联呢?
比如说,在类别中添加属性。
是的,你没有听错,就是在分类中添加属性!!!
以前我们看书看视频看博客,都知道 “分类中只能添加方法,不能添加属性”。但其实这么说不准确。
不能添加属性指的是,分类不会生成setter和getter方法,所以你添加的属性不能通过self.xxx的方式调用。
事实上在分类中不能添加属性是指编译时不能添加属性,而在运行时是可以动态添加的。
我们再来举个栗子:
我们定义了一个NSObject的分类叫CategoryWithProperty,在这个分类中我们添加了一个属性叫property。
正常情况下我们写setter方法和getter方法应该是这样的:
两段对照着来看会好理解一些。
setter方法无非就是把参数value的值赋给property,这不正好是objc_setAssociatedObject做的事吗?
同理,getter方法无非就是获取property的值,这不也正好是objc_getAssociatedObject做的事吗?!
我们在setter方法中把value这个值通过一个key关联到self这个源对象上,然后在getter方法中通过这个同样的key在源对象self中获取到这个值。一切都是如此的美妙。
也许你会对这个key有疑惑。(没有疑惑才不正常好吧?)
是的,通常情况下key推荐使用static char类型。但是在setter和getter这里有一个更简单的做法,那就是直接使用选择器(selector)。
因为SEL生成的时候就是一个唯一的常量,所以可以用_cmd作为objc_setAssociatedObject()的key。
我们来思考几个问题:
1.关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?
答:关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的。
2.关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?
答:关联对象的释放时机与移除时机并不总是一致。
关于这些问题,可以参考点击打开链接