关联:指把两个对象相互关联起来,使得其中的一个对象作为另外一个对象的一部分
一、在类的定义之外为类增加额外的存储空间
使用关联,我们可以不用修改类的定义而为其对象增加存储空间。这在我们无法访问到类的源码的时候或者是考虑到二进制兼容性的时候是非常有用。
关联是基于关键字的,因此,我们可以为任何对象增加任意多的关联,每个都使用不同的关键字即可。关联是可以保证被关联的对象在关联对象的整个生命周期 都是可用的(在垃圾自动回收环境下也不会导致资源不可回收)。
二、创建关联
创建关联要使用到Objective-C的运行时函数:objc_setAssociatedObject来把一个对象与另外一个对象进行关联。该函数需要四个参数:源对象,关键字,关联的对象和一个关联策略。
- 关键字:一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字。
- 关联策略:表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。这种关联策略是通过使用预先定义好的常量来表示的。
static char kExtendVarKey; // 键名
- (void)someCategoryMethod
{
NSString *extendVar = objc_getAssociatedObject(self, &kExtendVarKey);
if(!extendVar){
extendVar = @"someText";
objc_setAssociatedObject(self, &kExtendVarKey, extendVar, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void)someCategoryMethod
{
NSString *extendVar = objc_getAssociatedObject(self, &kExtendVarKey);
if(!extendVar){
extendVar = @"someText";
objc_setAssociatedObject(self, &kExtendVarKey, extendVar, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
}
而使用
_cmd
可以直接使用该@selector的名称,即someCategoryMethod
,并且能保证改名称不重复 - (void)someCategoryMethod
{
NSString *extendVar = objc_getAssociatedObject(self, _cmd);
if(!extendVar){
extendVar = @"someText";
objc_setAssociatedObject(self, _cmd, extendVar, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
{
NSString *extendVar = objc_getAssociatedObject(self, _cmd);
if(!extendVar){
extendVar = @"someText";
objc_setAssociatedObject(self, _cmd, extendVar, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
}
相关函数:
objc_getAssociatedObject、objc_setAssociatedObject、objc_removeAssociatedObjects都是Obj-c中的外联方法,
(1)objc_setAssociatedObject 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
(2)objc_getAssociatedObject 用于获取关联对象;
(3)objc_removeAssociatedObjects 用于移除一个对象的所有关联对象。
注意:objc_removeAssociatedObjects 函数我们一般是用不上的,因为这个函数会移除一个对象的所有关联对象,将该对象恢复成“原始”状态。这样做就很有可能把别人添加的关联对象也一并移除,这并不是我们所希望的。所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。
key 值 :这个 key 值必须保证是一个对象级别的唯一常量。
以下三种推荐的 key 值:
(1)声明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作为 key 值;
(2)声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作为 key 值;
(2)声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作为 key 值;
(3)用 selector ,使用 getter 方法的名称作为 key 值。
第三种省掉了一个变量名,非常优雅地解决了计算科学中的两大世界难题之一(命名)。
关联策略
结论
(1)关联对象的释放时机与被移除的时机并不总是一致的,比如上面的 self.associatedObject_assign 所指向的对象在 ViewController 出现后就 被释放了,但是 self.associatedObject_assign 仍然有值,还是保存的原对象的地址。如果之后再使用 self.associatedObject_assign 就会造成 Crash ,所以我们在使用弱引用的关联对象时要非常小心;
(2)一个对象的所有关联对象是在这个对象被释放时调用的 _object_remove_assocations 函数中被移除的。
(3)关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的;
- (4)关联对象的五种关联策略与属性的限定符非常类似,在绝大多数情况下,我们都会使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的关联策略,这可以保证我们持有关联对象;
Objective-C的编译器在编译后会在每个方法中加两个隐藏的参数:
一个是_cmd,当前方法的一个SEL指针。_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例。
另一个就是用的比较多的self,指向当前对象的一个指针。