iOS底层-关联对象

关联对象

  1. 思考:如何实现给分类“添加成员变量”?
  2. 先看下面几种实现的方法

     //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);
           
     }
    
    1. 从上面的来看,采用全局变量、全局字典来解决分类添加成员变量都有弊端
  3. 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
    1. 关联对象提供了以下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)
      
    2. const void * key这个key通常传什么呢?
      1. 用法1:

         //将namekey变量的地址值存入到当前变量的内存中
         static void *MyKey = &MyKey;
         objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
         objc_getAssociatedObject(obj, MyKey)
        
      2. 用法2:

         //static char MyKey;//直接传入地址值
         objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
         objc_getAssociatedObject(obj, &MyKey)
        
      3. 用法3:

         //使用属性名作为key: 常量字符串,在内存中只有一份
         objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
         objc_getAssociatedObject(obj, @"property");
        
      4. 用法4:

         //使用get方法的@selecor作为key
         objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
         objc_getAssociatedObject(obj, @selector(getter))
        
    3. 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
      
  4. 关联对象的原理
    1. 打开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);
       }
      
    2. 进入_object_set_associative_reference

       //里面全部是C++,以后有C++经验了在分析
      
      1. 实现关联对象技术的核心对象有

         AssociationsManager 
         AssociationsHashMap 
         ObjectAssociationMap
         ObjcAssociation
        
      2. 之间的关系 图1

    3. 总结:

      1. 关联对象并不是存储在被关联对象本身内存中
      2. 关联对象存储在全局的统一的一个AssociationsManager中
      3. 设置关联对象为nil,就相当于是移除关联对象

补充:static修饰全局变量的作用

  1. 结论:全局变量默认是extern修饰,可以直接被外部访问;而static修饰全局变量只能被当前文件访问。
  2. 疑问:全局变量没有被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;
     }
    
Table of Contents