OC原理-Category关联对象

458 阅读3分钟

一、引入

通过OC原理-Category我们可以看到分类的本质结构如下,我们看到properties说明分类中可以声明属性

//分类底层机构
struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;//存放实例方法
	const struct _method_list_t *class_methods;//存放类方法
	const struct _protocol_list_t *protocols;//存放协议
	const struct _prop_list_t *properties;//存放声明的属性
};
@interface Person (test1)
@property(nonatomic,copy)NSString *name;
@end
//调用
Person *person = [[Person alloc] init];
person.name = @"zht";

居然报错了,说明在分类中添加属性跟在原类中添加属性是不一样的。

在原类中添加属性做了三件事:1.声明了一个_key的成员变量 2.声明了setKeykey的方法 3.实现了setKeykey方法
@property = ivar(实例变量)+getter/setter;
@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果都没有写默认就是@sythesize var = _var;
@synthesize表示如果属性没有手动实现setter和getter方法,编译器会自动加上这两个方法,并产生成员变量。
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。


在分类中添加属性只做了一件事:声明了setKey和key的方法。

那我们在分类中手动添加_key的成员变量,添加set/get的方法实现。 报错了!!!成员变量不能放到分类中!!!我们看分类的底层结构也没发现存放成员变量的地方,要想在分类中实现像原类中那样的属性,得利用runtime进行对象关联。

二、关联对象

API

//添加关联对象 object:要被添加关联对象的主体  key:一个指针,用来标示这个属性  value:关联对象  policy:关联策略
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
//获取关联对象
id objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

//移除所有的关联对象 
void objc_removeAssociatedObjects(id _Nonnull object)

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403
};

关联策略对应关系如下

举例

@interface Person (test1)
@property(nonatomic,assign)int age;
@property(nonatomic,copy)NSString *name;
@end

@implementation Person (test1)
static const void *NameKey = &NameKey; // 使用NameKey的内存地址当key   加上static,NameKey只能在当前类使用  这里相当于声明了一个指针
static const void *AgeKey = &AgeKey; //使用AgeKey的内存地址当key
-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, NameKey);
}
-(void)setAge:(int)age{
    objc_setAssociatedObject(self, AgeKey, @(age), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(int)age{
    return [objc_getAssociatedObject(self, AgeKey) intValue];
}
@end

还可以这么写。节约内存

@implementation Person (test1)
static const char NameKey; //这样相对上面声明了一个char类型的变量,节省内存
static const char AgeKey;  
-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, &NameKey);
}
-(void)setAge:(int)age{
    objc_setAssociatedObject(self, &AgeKey, @(age), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(int)age{
    return [objc_getAssociatedObject(self, &AgeKey) intValue];
}

还可以这么写 不用定义静态全局变量

@implementation Person (test1)
-(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)setAge:(int)age{
    objc_setAssociatedObject(self, @selector(age), @(age), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(int)age{
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

三、关联对象底层原理

实现关联对象技术的核心对象有

AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjectAssociation

它们之间的关系如下:

结论:关联对象并不是被存储在被关联对象本身内存中,关联对象存储在全局统一的一个AssocaitionManager中。设置关联为nil,就相当于移除关联对象。从这里也可以看出关联对象(name)也没有被(person对象)强引用,而且当person对象被销毁后,AssociationsHashMap中代表person对象的key、value也会被移除。