先看一个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分类添加“成员变量”,有两种方式:
- 方式一:可以通过在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分类里面每添加一个“成员变量”,就需要添加一个全局的字典对象。
- 方式二:用<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;
}