Runtime04Category和关联对象

先看一个demo

demo的目录结构如下
在这里插入图片描述
具体代码如下
main.m :

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Test.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        person.age = 12;
        person.weight = 34;
        NSLog(@"person age = %d, weight = %d", person.age, person.weight);
    }
    return 0;
}

Person类:

@interface Person : NSObject
@property(nonatomic, assign) int age;
@end
//---------分割线,分隔一个类的.h文件和.m文件-------

#import "Person.h"
@implementation Person
@end

Person (Test)分类:

@interface Person (Test)
@property(nonatomic, assign) int weight;
@end
//---------分割线,分隔一个类的.h文件和.m文件-------

#import "Person+Test.h"
@implementation Person (Test)
-(void)setWeight:(int)weight {
}

-(int)weight {
    return 8899;
}

@end

打印结果如下:
在这里插入图片描述
分析:

  • 编译器在Person.h文件中解析到@property(nonatomic, assign) int age;时,会在Person.h文件生成age成员变量及其对应的get与set方法的声明,同时也会在Person.m文件生成get和set方法实现的代码,此时编译器为我们生成的Person.h文件和Person.n文件的代码如下面所示:
//在Person.h文件中生成
@interface Person : NSObject {
    int _age;
}
 - (void)setAge:(int)age;
 - (int)age;
 
//在Person.m文件中生成
@implementation Person
-(void) setAge:(int)age {
    _age = age;
}
-(int) age {
    return _age;
}
@end
  • 编译器在编译Person+Test.h文件的 @property(nonatomic, assign) int weight;这一行代码时,只会在Person+Test.h文件中生成weight的get和set方法的声明,而没有生成方法的实现,也没有生成weight成员变量,此时编译器为我们生成的Person+Test.h文件代码如下所示:
@interface Person : NSObject
 - (void)setWeight:(int)weight;
 - (int)weight;
 @end

为什么Category不能生成成员变量,因为从Runtime源码的Category的结构体(如下代码所示)可以看出该结构体并没有包含成员变量列表。

//Runtime源码中为分类定义的结构体,一个项目的所有分类都共用该结构体,每一个分类都是该结构体的一个实例。每一个实例都有属于自己的信息,所以每一个分类都有属于自己的信息。
struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

所以当打印person.weight时,本质上调用的是Person+Test.m里面的weight方法,该方法返回的是8899。

如果想给Person+Test分类添加“成员变量”,有两种方式:

  1. 方式一:可以通过在Person+Test.m文件里面定义一个全局变量。Person+Test.m文件修改为
#import "Person+Test.h"

@implementation Person (Test)

NSMutableDictionary *weights_;

+ (void)initialize {//Person类第一次接收到消息时该方法会被调用
    weights_ = [NSMutableDictionary dictionary];  //初始化全局变量,即一个全局的字典对象
}

-(void)setWeight:(int)weight {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    weights_[key] = @(weight);
}

-(int)weight {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return [weights_[key] intValue];
}

@end

此时的打印结果如下图,虽然能达到每一个Person实例对象都有属于自己的weight的值,但是在Person+Test分类里面每添加一个“成员变量”,就需要添加一个全局的字典对象。
在这里插入图片描述

  1. 方式二:用<objc/runtime.h> 里面的关联对象的方法。
    Person+Test.h文件添加一个name属性,文件修改后的代码如下:
#import "Person.h"

@interface Person (Test)
@property(nonatomic, assign) int weight;
@property(nonatomic, copy) NSString *name; //新增属性

@end

Person+Test.m文件使用关联对象的set和get方法,修改后的代码如下(版本①):

#import "Person+Test.h"
#import "objc/runtime.h"

@implementation Person (Test)
//地址值是唯一的
static const char nameKey;//只能在当前文件使用的全局变量,我只要的是nameKey的地址的值,不关心nameKey的值
static const char weightKey;//只能在当前文件使用的全局变量,我只要的是weightKey的地址的值,不关心weightKey的值

- (void)setName:(NSString *)name {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    return objc_getAssociatedObject(self, &nameKey);
}

-(void)setWeight:(int)weight {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    objc_setAssociatedObject(self, &weightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(int)weight {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    return [objc_getAssociatedObject(self, &weightKey) intValue];
}

@end

Person+Test.m文件使用关联对象的set和get方法,修改后的代码如下(版本②):

#import "Person+Test.h"
#import "objc/runtime.h"

@implementation Person (Test)

- (void)setName:(NSString *)name {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    return objc_getAssociatedObject(self, @selector(name));
}

-(void)setWeight:(int)weight {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(int)weight {
	//第2个参数是void* 类型,第2个参数是什么值无所谓,但是必须确保同一个属性的get和set的第2个参数的值是相同的。
    return [objc_getAssociatedObject(self, @selector(weight)) intValue];
}
@end

Person+Test.m文件使用关联对象的set和get方法,修改后的代码如下(版本③):

#import "Person+Test.h"
#import "objc/runtime.h"

@implementation Person (Test)

- (void)setName:(NSString *)name {
	//_cmd 等价于 @selector(setName:),
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    //_cmd 等价于 @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

-(void)setWeight:(int)weight {
	//_cmd 等价于 @selector(setWeight:),
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(int)weight {
    //_cmd 等价于 @selector(weight)
    return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end

此时的打印结果为
在这里插入图片描述

关联对象的原理

先看一个demo

Person类的一个名为Person+Test的分类的.h文件(即Person+Test.h文件)如下图
在这里插入图片描述
Person+Test.m文件如下图
在这里插入图片描述

Demo的运行结果如下图

在这里插入图片描述

关联对象的objc_setAssociatedObject()函数解析

objc_setAssociatedObject()和objc_getAssociatedObject()函数内部由4个关键的类来实现,这4个类分别是AssociationsManager、AssociationsHashMapAssociationsHashMap、ObjectAssociationMap和ObjcAssociation类
其中,AssociationsManager 是管理关联对象的类。AssociationsHashMap类似于java的HashMap,key是你调用objc_setAssociatedObject()或者objc_getAssociatedObject()函数所传进来的第1个参数(id类型),value是ObjectAssociationMap的实例对象。ObjectAssociationMap也类似于java的HashMap,key是你调用objc_setAssociatedObject()或者objc_getAssociatedObject()函数所传进来的第2个参数,value是ObjcAssociation的实例对象。ObjcAssociation是一个封装了修饰属性的策略(比如copy、strong、assign等,策略的值是你调用objc_setAssociatedObject()函数传进来的第4个参数)和”你要在分类中添加的成员变量的值“(即你调用objc_setAssociatedObject()函数传进来的第3个参数)的类。
这4个类的关系如下图所示
在这里插入图片描述
在这里插入图片描述
_object_set_associative_reference函数的具体实现在runtime源码工程的objc-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函数的实现在objc-references.mm文件中

//AssociationsManager 是管理关联对象的类
//AssociationsHashMap类似于java的HashMap,key是你传进来的object参数,value是ObjectAssociationMap的实例对象
//ObjectAssociationMap也类似于java的HashMap,key是你传进来的key参数,value是ObjcAssociation的实例对象
//ObjcAssociation是一个封装了修饰属性的策略(比如copy、strong、assign等)和objc_setAssociatedObject()函数的第4个参数的类
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager; 
        AssociationsHashMap &associations(manager.associations()); 
        disguised_ptr_t disguised_object = DISGUISE(object); //DISGUISE()是一个内联函数,将传入的object对象的地址取反后返回
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);  //获取你传进来的object参数对应的ObjectAssociationMap的实例对象
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ashMap
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else { //如果你传进来的第4个参数为空,那么该你在分类中添加的“成员变量”就会被移除
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); } //DISGUISE()是一个内联函数

关联对象的objc_getAssociatedObject()函数解析

objc_getAssociatedObject()函数也在objc-runtime.mm文件中,具体实现如下:

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

_object_get_associative_reference()函数在objc-references.mm中,具体实现如下:

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object); //根据object对象获取对应的AssociationsHashMap实例对象
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) { //如果能找到你之前调用objc_setAssociatedObject()时存入的值(value)
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { //根据你之前调用objc_setAssociatedObject()时传入的策略来处理value
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值