一、引入
通过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.声明了setKey和key的方法 3.实现了setKey和key方法
@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也会被移除。