一、添加属性
1.1、类中添加属性
在类中添加一个 name 属性时,具体做了哪些事呢?
1、定义一个 _name 成员变量
2、分别生成一个setter 方法和一个 getter 方法的声明
3、生成以上两个方法的具体实现。
如在 Person.h 添加一个 age 属性:
@interface Person : NSObject @property (assign, nonatomic) int age; @end
编译后,代码会直接帮我们生成如下结构:
Person.h :
@interface Person : NSObject { int _age; } - (void)setAge:(int)age; - (int)age; @end
Person.m :
#import "Person.h" @implementation Person - (void)setAge:(int)age { _age = age; } - (int)age { return _age; } @end
1.2、Category对象中添加属性
那么在Category中添加属性呢?他只会帮我们生成方法的声明,类似如下:
- (void)setAge:(int)age; - (int)age;
没有变量和方法的实现。看来,在Category中是无法直接通过声明属性的方式记录成员变量。
那么就无法在Category中添加属性了吗?
在默认情况下,因为Category底层结构的限制,不能添加成员变量到Category中。但可以通过关联对象来间接实现。
二、关联对象
2.1、关联对象API
关联对象提供了以下API:
// 添加关联对象 // object:对象,即我们要给哪个对象关联属性的对象。 // key:定义的检索键 // value:关联的属性值,即 setter 方法传进来的值 // policy:关联策略,见下表 void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy) // 获得关联对象 id objc_getAssociatedObject(id object, const void * key) // 移除所有的关联对象 void objc_removeAssociatedObjects(id object)
2.2、key的多种赋值方式
通过关联对象来间接实现Category也能添加成员变量。
key有多种赋值方式,我们来看下key的几种常见用法:
1、赋值变量key为自己的地址值
// static:设置变量的作用域为当前文件 static void *MyKey = &MyKey;// MyKey对象必须赋值,否者默认为0,当设置多个成员变量会有问题。 objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, MyKey)
2、定义key为 char 类型(1个字节),减小占用字节
static char MyKey;// 定义变量时,即生成一个地址值 objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, &MyKey)
3、使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_getAssociatedObject(obj, @"property");
注意:因为直接写出来的字符串,其地址是是放在数据常量区的,这个地址和值是不变的。即使是多次使用,其地址和值都是不变的。
4、使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, @selector(getter)) // _cmd:表示当前方法 @selector // _cmd == @selector(getter) objc_getAssociatedObject(obj,_cmd)
2.3、实战引用
新建 Person 类 和 Person+Test 分类
1、 Person.h:
#import <Foundation/Foundation.h> @interface Person : NSObject @property (assign, nonatomic) int age; @end
2、 Person.h:
#import "Person.h" @implementation Person @end
3、Person+Test.h:
#import "Person.h" @interface Person (Test) @property (assign, nonatomic) int weight; @property (copy, nonatomic) NSString* name; @end
4、Person+Test.m:
#import "Person+Test.h" #import <objc/runtime.h> @implementation Person (Test) - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)name { // 隐式参数 // _cmd == @selector(name) return objc_getAssociatedObject(self, _cmd); } - (void)setWeight:(int)weight { objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (int)weight { // _cmd == @selector(weight) return [objc_getAssociatedObject(self, _cmd) intValue]; } @end
5、引用赋值
#import <Foundation/Foundation.h> #import "Person.h" #import "Person+Test.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; person.age = 10; person.name = @"lisa"; person.weight = 110; Person *person2 = [[Person alloc] init]; person2.age = 20; person2.name = @"tony"; // person2.name = nil; person2.weight = 220; NSLog(@"person>>> age:%d, name:%@, weight:%d", person.age, person.name, person.weight); NSLog(@"person2>>> age:%d, name:%@, weight:%d", person2.age, person2.name, person2.weight); } return 0; }
三、关联对象的原理
实现关联对象技术的核心对象有:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
objc4源码解读:objc-references.mm,关键代码如下:
通过源码解析,我们可以知道,在调用关联对象api的:
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
各个核心对象的关系图如下:
总结:
1、关联对象并不是存储在被关联对象本身内存中。
2、关联对象存储在全局的统一的一个AssociationsManager中。
3、设置关联对象属性为 nil,就相当于是移除 ObjectAssociationMap 中对应属性的关联对象
4、当调用 void objc_removeAssociatedObjects(id object) 时,是移除 object 对象中的所有关联对象。即移除 AssociationsHashMap 对象中,以 DISGUISE(object)
为键的 ObjectAssociationMap 值。
四、拓展
1、Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果。
使用关联对象的方式实现给Category添加成员变量。