iOS底层第七天 -- 分类Category之关联
引导语:
我们能否给分类添加属性呢?
想要回答这个问题,我们先了解一下,声明属性时候系统都具体做了什么?
Q:类声明属性时候,系统都具体做了什么?
整个属性的生成过程在runtime中分为以下几步:
- 创建该属性,设置其objc_ivar,通过偏移量和内存占用就可以方便获取。
- 生成其getter和setter。
- 将属性的ivar添加到类的ivar_list中,作为类的成员变量存在。
- 将getter和setter加入类的method_list中。之后可以通过直接调用或者点语法来使用。
- 将属性的描述添加到类的属性描述列表中。
Q:分类声明属性,系统都做了什么?
分类声明属性,系统只生成setter和getter方法的声明,但是成员变量、setter和getter方法的实现均没有。
我们如何给分类添加属性呢?
我们研究如何给分类添加成员属性的时候,由分类结构体内数据结构发现,成员属性无法存在,但是set、get方法我们可以手动添加进去。
通过研究需求我发现:我们需要的是一种数据结构,这种结构能保存对应变化的属性值,同时需要保证有个唯一的key值能取出这个属性值。 字典结构可以满足*,我们将对象instance的地址作为key,变化属性值作为value。
Q:为什么不能用字典为分类增加实例变量
- 字典为分类增加实例变量存在全局变量中,内存泄漏
- 多线程访问会存在同时访问变量的情况,还得加锁处理
- 每增加一个变量,字典、setter、getter方法就得重新编写
小知识点:A文件中定义某个成员
int abc;
,我们可以在项目其他地方通过extern int abc; abc = 10;
进行更改。如果我们只允许abc在A文件执行,则需要修改此属性staic int abc;
Q:如何给分类关联对象?
- 添加关联对象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
// object:当前对象
// key:标记key.这里传入的const void *代表传入&地址
//value :关联属性值
//objc_AssociationPolicy:关联对象策略
-
获得关联对象
id objc_getAssociatedObject(id object, const void * key)
-
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
关联对象策略
保证关联对象key唯一的其他方法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
//static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
//使用属性名作为key
//直接使用的@"name"类似的变量是存在常量区的,所以地址会相同
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
//使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
objc_getAssociatedObject(self, _cmd);
//_cmd:表示当前方法的selector方法
Q:关联对象的原理
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
objc4源码解读:objc-references.mm
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);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
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 {
// 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);
}
核心对象内部:
//AssociationsManager内包含AssociationsHashMap
class AssociationsManager {
static AssociationsHashMap *_map;
}
//AssociationsHashMap内包含ObjectAssociationMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>
//ObjectAssociationMap内包含ObjcAssociation
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
//ObjcAssociation 内包含策略和属性值
class ObjcAssociation {
uintptr_t _policy;
id _value;
}
关联对象的原理.png
Q:关联对象是否储存在类对象内存中?
A:不是的
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个AssociationsManager,AssociationsHashMap中
Q:设置关联对象为nil,会发生什么?
A:相当于是移除关联对象,内部会有一个erase操作
Q:如何移除所有关联对象?
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
Q:如果类对象销毁,分类的关联对象会移除么?
A:会的
6.category 分类能否添加成员变量?如果可以,如何给category添加成员变量?
- 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
- 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现