iOS底层-关联对象
关联对象
- 思考:如何实现给分类“添加成员变量”?
-
先看下面几种实现的方法
//Person的分类 #import "Person.h" @interface Person (Extern) //分类中写属性,只会生成get、set方法的声明,其他的都不会生成 @property (nonatomic, assign) int height; @property (nonatomic,copy) NSString *name; @end #import "Person+Extern.h" //解决方法1:使用全局变量 //弊端:当创建多个对象时多个对象会拥有一个值!!! //int g_height ; //解决方法2:使用字典 #define PKEY [NSString stringWithFormat:@"%p",self] NSMutableDictionary *weights_; NSMutableDictionary *names_; @implementation Person (Extern) +(void)load{ weights_ = [NSMutableDictionary dictionary]; } -(void)setHeight:(int)height{ // g_height = height; weights_[PKEY] = @(height); } -(int)height{ // return g_height; return (int)[weights_[PKEY] integerValue]; } -(void)setName:(NSString *)name{ names_[PKEY] = name; } -(NSString *)name{ return names_[PKEY] ; } @end //情况1: Person+Extern 中只声明一个height属性 void test1(){ Person *person = [[Person alloc] init]; //这么设置会报错,因为分类中只只实现了set方法的声明,并没有set方法的实现,因此报错: // -[Person setHeight:]: unrecognized selector sent to instance 0x10051c030 person.height = 10; } /** 情况2: Person+Extern 中声明一个height属性 实现中增加一个全局变量g_height用于保存传值 手动实现get、set方法,set方法通过g_height来接收值,get方法直接返回g_height值 弊端: 多个对象会拥有同一个值 */ void test2(){ Person *person1 = [[Person alloc] init]; person1.height = 10; Person *person2 = [[Person alloc] init]; person2.height = 20; //打印结果: ===person1-height:20===person2-height:20 ,不同的对象会有同一个值 NSLog(@"===person1-height:%d===person2-height:%d",person1.height,person2.height); } /** 情况3: Person+Extern 中声明一个height属性 实现中增加一个全局字典weights_用于保存传值 手动实现get、set方法,set方法通过weights_来接收值,get方法返回weights_的key对应的值 问题: 1. 线程安全问题:n个对象同时访问那个全局的字典 2. 当这个对象分类有多个属性时,需要设置n个全局字典 */ void test3(){ Person *person1 = [[Person alloc] init]; person1.height = 10; person1.name = @"111"; Person *person2 = [[Person alloc] init]; person2.height = 20; person2.name = @"123"; //打印结果:===person1-height:10===person2-height:20,使用字典可以实现 NSLog(@"===person1-height:%d===person2-height:%d",person1.height,person2.height); }
- 从上面的来看,采用全局变量、全局字典来解决分类添加成员变量都有弊端
- 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
-
关联对象提供了以下API
1. 添加关联对象 void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy) 2. 获得关联对象 id objc_getAssociatedObject(id object, const void * key) 3. 移除所有的关联对象 void objc_removeAssociatedObjects(id object)
const void * key
这个key通常传什么呢?-
用法1:
//将namekey变量的地址值存入到当前变量的内存中 static void *MyKey = &MyKey; objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, MyKey)
-
用法2:
//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))
-
-
objc_AssociationPolicy参数
objc_AssociationPolicy 对应的修饰符 OBJC_ASSOCIATION_ASSIGN assign OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic OBJC_ASSOCIATION_RETAIN strong, atomic OBJC_ASSOCIATION_COPY copy, atomic
-
- 关联对象的原理
-
打开objc4,找到runtime.h文件,找到objc_setAssociatedObject方法的声明,然后进入定位到runtime.mm
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); }
-
进入_object_set_associative_reference
//里面全部是C++,以后有C++经验了在分析
-
实现关联对象技术的核心对象有
AssociationsManager AssociationsHashMap ObjectAssociationMap ObjcAssociation
-
之间的关系
-
-
总结:
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个AssociationsManager中
- 设置关联对象为nil,就相当于是移除关联对象
-
补充:static修饰全局变量的作用
- 结论:全局变量默认是extern修饰,可以直接被外部访问;而static修饰全局变量只能被当前文件访问。
-
疑问:全局变量没有被static修饰时,如何被外部访问的呢?平时使用时,貌似也不能用啊?下面例证:
//Cat.h文件 @interface Cat : NSObject -(void)setGlobalV; @end //Cat.m文件 #import "Cat.h" //全局变量 int catAge_ ; @implementation Cat -(void)setGlobalV{ //给全局变量设置值 catAge_ = 10; } @end //main文件 #import <Foundation/Foundation.h> #import "Cat.h" //访问非static修饰的全局变量:只要在当前访问的文件中定义一个用extern修饰的相同名字的变量即可访问 extern int catAge_; int main(int argc, const char * argv[]) { @autoreleasepool { Cat *cat = [Cat new]; [cat setGlobalV]; //打印为10,说明成功的访问了Cat.m文件内部的全局变量catAge_ NSLog(@"=======%d",catAge_); } return 0; }