Objective-C (二)扩展与分类
2020-01-14 本文已影响0人
造轮子的锅
分类
1.分类数据结构
struct objc_category {
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;
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);
}
2.分类添加方法的原理:
- 编译期过程
- 编译器生成实例方法列表OBJC$CATEGORY_INSTANCE_METHODS"className"$"categoryName"和属性列表OBJC$PROP_LIST"className"$"categoryName";用以填充我们在类中定义的方法跟属性。
- 编译器生成category本身,并用前面生成的方法列表跟属性列表初始化category本身
- 编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$,用于运行期的加载.(关于DATA段以及以下dyld的内容,会在mach-O的篇章介绍)
- 运行期过程
- 拿到catlist上的category_t数组,把category_t上的实例方法,协议以及属性添加到类上
- 把category_t的类方法和协议添加到类的metaclass上
- 运行期添加分类过程
- addUnattachedCategoryForClass将类和category做一个映射
- remethodizeClass负责处理添加事宜
- 添加实例方法会调用attachCategoryMethods,它将所有category的实例方法列表拼成一个大的实例方法列表,然后转交给attachMethodList
3.添加属性
我们都知道无法给分类添加成员变量,因为category并不会为我们生成set,get方法,这个时候可以用关联对象来实现
objc_setAssociatedObject();
objc_getAssociatedObject();
关联对象的原理分析,内存管理可看以下章节
4.load方法与intialize方法的比较
5.补充的点
- 分类的方法只会添加,不会覆盖原先的方法,调用同名方法时调用的是最后编译的分类文件中的方法实现。
- intialize方法会先调用父类,再子类,所以子类未实现时,父类会被多次调用。
类扩展与分类区别
- 扩展可以当成一个匿名的类,但是必须要有该类的源码的时候才能编写扩展。(扩展不能拥有独立的实现部分)
- 类扩展是在编译期添加到类中,而分类是在运行时添加
- 写在.m的类扩展方法与属性是私有的
关联对象
- 关联对象保存在什么地方
我们先看看函数_object_set_associative_reference
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);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
} 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管理,定义如下
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
- 关联对象的内存管理
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
// Read all of the flags at once for performance.
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}
runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。