在我们的现实开发中Category分类的利用必不可少,那么我们通过以下几个方面来探索一下分类
- 1.什么是分类Category
- 2.Category的作用
- 3.Category和Exension的区别
- 4.Category底层探究
- 5.关联对象的探索
什么是分类(Category)
Category是Ovjective-C 2.0 之后添加的语言特性,Category作用是为已经存在的类添加方法
Category的作用
- 1.可以淘汰单个文件的体积
- 2.可以把差别的功能构造到差别的Category中
- 3.可以按需加载
- 4.声明私有方法
- 5.把framework的私有方法公开
Category和Exension的区别
- 1.Category依托当前类存在,Exension则是类的一部分
- 2.Category在运行时期确定,Exension在编译器确定
- 3.Category不能添加变量,Exension可以添加变量(编译时期对象的内存结构已经确定)
Category底层探究
4.1 Category的数据结构
Category的数据结构如下
struct category_t { //当前类的名称 const char *name; //须要扩展的类对象,在编译期没有值 classref_t cls; //实例方法 WrappedPtr<method_list_t, method_list_t: trauth> instanceMethods; //对象方法 WrappedPtr<method_list_t, method_list_t: trauth> 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); protocol_list_t *protocolsForMeta(bool isMeta) { if (isMeta) return nullptr; else return protocols; }};
- const char *name;: 当前类的名称
- classref_t cls;:须要扩展的类对象,在编译期没有值
- WrappedPtr<method_list_t, method_list_t:
trauth> instanceMethods;:实例方法列表
- WrappedPtr<method_list_t, method_list_t:
trauth> classMethods;:对象方法列表
- struct protocol_list_t *protocols;:协议列表
- struct property_list_t *instanceProperties;:实例属性
- struct property_list_t *_classProperties;:类属性
4.2 Category在编译时期做了什么
#import " erson+A.h"@implementation Person (A)- (void)funcA { NSLog(@"A");}@end起首我们创建如许一个分类,下面我们通过clang看下在编译时期分类到底做了些什么
clang -rewrite-objc "文件名.m/h"我们可以通过上面这个下令在终端中获取到.cpp文件
static struct _category_t _OBJC_$_CATEGORY_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = { " erson", 0, // &OBJC_CLASS_$_Person, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A, 0, 0, 0,};我们编译后的东西都存放在Mach-o的可实验文件中,通过字段标识当前内容。上述内容就是存在__DATA中的__objc_const的字段中
- Person ->name字段 类的名称
- 0, // &OBJC_CLASS_$_Person, ->cls 须要扩展的类对象,在编译期没有值
- (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A, -> instanceMethods 在分类中声明的实例方法
我们再来看下实例方法列表这个结构体里有些啥
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"funcA", "v16@0:8", (void *)_I_Person_A_funcA}}};
- funcA方法编号
- v16@0:8方法署名
- (void *)_I_Person_A_funcA 真实的函数所在
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { &_OBJC_$_CATEGORY_Person_$_A,};将编译期生成的分类结构体存放在__DATA中的__objc_catlist字段中
总结 Category在编译期做了什么
- 1.把分类编译成结构体
- 2.在对应的字段中添补相应的数据
- 3.把全部的分类存放在__DATA数据段中的__objc_catlist字段中
4.3 Category在运行时期做了什么
要想探索Category在运行时期做了什么,起首我们来倒app的入口函数_objc_init
void _objc_init(void){ static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? //情况变量初始化 environ_init(); //线程寄存器初始化 tls_init(); //静态初始化 static_init(); //runtime初始化 runtime_init(); //异常初始化 exception_init();#if __OBJC2__ cache_t::init();#endif _imp_implementationWithBlock_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__ didCallDyldNotifyRegister = true;#endif}起首辈行了一些初始化的操纵,environ_init (),tls_init (),static_init (),runtime_init (),exception_init.
然后调用_dyld_objc_notify_register(&map_images, load_images, unmap_image);函数,通过dyld注册三个函数:map_images(map映射,当前dyld把images(镜像)加载进内存时,会出发map_images的回调), load_images(当dyld初始化images(镜像)模块时调用,+load方法也会在这里被调用), unmap_image(dyld把image移除内存时调用)
这里我们重点关注map_images
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]){ static bool firstTime = YES; //头文件信息 header_info *hList[mhCount]; //头文件数 uint32_t hCount; size_t selrefCount = 0; // Find all images with Objective-C metadata. hCount = 0; // Count classes. Size various table based on the total. //类的数量 int totalClasses = 0; if (hCount > 0) { //读取镜像文件,读取OC相干的Section _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } }由于map_images_nolock函数中代码太多,截取了部分关键代码
_read_images函数中做了什么
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){ header_info *hi; uint32_t hIndex; #define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++ if (didInitialAttachCategories) { //遍历整个头文件列表 for (EACH_HEADER) { //加载分类 load_categories_nolock(hi); } }}由于_read_images函数中代码太多,截取了部分关键代码
- EACH_HEADER是一个宏界说,用来遍历整个头文件列表
- load_categories_nolock加载分类
接着往下看 load_categories_nolock
static void load_categories_nolock(header_info *hi) { bool hasClassProperties = hi->info()->hasCategoryClassProperties(); size_t count; auto processCatlist = [&](category_t * const *catlist) { for (unsigned i = 0; i < count; i++) { category_t *cat = catlist; Class cls = remapClass(cat->cls); locstamped_category_t lc{cat, hi}; if (!cls) { // Category's target class is missing (probably weak-linked). // Ignore the category. if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // Process this category. if (cls->isStubClass()) { // Stub classes are never realized. Stub classes // don't know their metaclass until they're // initialized, so we have to add categories with // class methods or properties to the stub itself. // methodizeClass() will find them and add them to // the metaclass as appropriate. if (cat->instanceMethods || cat->protocols || cat->instanceProperties || cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { objc::unattachedCategories.addForClass(lc, cls); } } else { // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if // the class is realized. if (cat->instanceMethods || cat->protocols || cat->instanceProperties) { if (cls->isRealized()) { attachCategories(cls, &lc, 1, ATTACH_EXISTING); } else { objc::unattachedCategories.addForClass(lc, cls); } } if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { if (cls->ISA()->isRealized()) { //把分类中声明的属性,方法添加到累的属性,方法列表中 attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); } else { //把当前分类加载到对应的类中 objc::unattachedCategories.addForClass(lc, cls->ISA()); //addForClass底层数据结构的映射 } } } } }; //读取分类列表 processCatlist(hi->catlist(&count)); processCatlist(hi->catlist2(&count));}在这个函数中做了三件事
- 1.processCatlist(hi->catlist(&count));,processCatlist(hi->catlist2(&count));读取分类列表
- objc::unattachedCategories.addForClass(lc, cls->ISA());当前分类加载到对应的类中
- attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); 把分类中声明的属性,方法,协议添加到类的属性,方法,协议列表中
attachCategories函数的实现
static voidattachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags){ //声明 方法列表,属性列表,协议列表所用的空间 constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; property_list_t *proplists[ATTACH_BUFSIZ]; protocol_list_t *protolists[ATTACH_BUFSIZ]; if (mcount > 0) { //准备当前的方法列表 prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle, __func__); //把分类方法追加进类的方法列表中 rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) { flushCaches(cls, __func__, [](Class c){ // constant caches have been dealt with in prepareMethodLists // if the class still is constant here, it's fine to keep return !c->cache.isConstantOptimizedCache(); }); } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);}由于attachCategories函数中代码太多,截取了部分关键代码
- 1.prepareMethodLists准备当前的方法列表,焦点:把方法名称放到方法列表中
- 2.attachLists分类和本类相应的对象方法,属性,和协议举行了归并。
attachLists内部实现
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists //多对多 uint32_t oldCount = array()->count; //获取原来方法列表中的方法个数 uint32_t newCount = oldCount + addedCount; //得到添加后的方法个数 array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); //开发新的空间 newArray->count = newCount; array()->count = newCount; for (int i = oldCount - 1; i >= 0; i--) newArray->lists[i + addedCount] = array()->lists; //把原来的方法放在反面 for (unsigned i = 0; i < addedCount; i++) newArray->lists = addedLists; //把新添加的方法放在数组的前面 free(array()); setArray(newArray); validate(); } else if (!list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; validate(); } else { // 1 list -> many lists Ptr<List> oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; for (unsigned i = 0; i < addedCount; i++) array()->lists = addedLists; validate(); } }这里会分三种情况:多对多,0对1,1对多,这里就以多对多为例
- uint32_t oldCount = array()->count;获取原来方法列表中的方法个数
- uint32_t newCount = oldCount + addedCount得到添加后的方法个数
- array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));开发新的空间
- newArray->lists[i + addedCount] = array()->lists;把原来的方法放在反面
- newArray->lists = addedLists 把新添加的方法放在数组的前面
总结Category在运行时做了什么
1.通过catlist,catlist2 (_getObjc2CategoryList)读取分类列表
2.通过addForClass当前分类加载到对应的类中
3.attachCategories把分类中声明的属性,方法,协议添加到类的属性,方法,协议列表中
4.prepareMethodLists准备当前的方法列表,焦点:把方法名称放到方法列表中
5.attachLists分类和本类相应的对象方法,属性,和协议举行了归并
6.归并的顺序,先为分类中的方法,再是原类中的方法(这就是同名方法分类会优先于本类调用的原因)
多个分类/类也有同名方法,方法查找,本质上调用Runtime遍历方法列表,通过二分查找算法,不会立马返回imp指针,往前移动,找是否有同名方法。
关联对象的探索
@interface Person : NSObject@property (nonatomic, copy) NSString *name;@end在类中声明一个属性,体系会主动帮我们生成getter setter ivar.
当我们利用分类想为原类添加属性时,我们则须要利用关联对象的方式,那么关联对象和直接声明数据原理是否一样呢,我们接下来分析下
分类中我们利用objc_setAssociatedObject,objc_getAssociatedObject这两函数添加属性
起首我们来看下关联对象怎么存的objc_setAssociatedObject
void_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){ // This code used to work when nil was passed for object and key. Some code // probably relies on that to not crash. Check and handle it explicitly. // rdar://problem/44094390 if (!object && !value) return; if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); DisguisedPtr<objc_object> disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; //把外部传入的value和战略存起来 // retain the new value (if any) outside the lock. //根据上面存的policy,设置内存管理语义 association.acquireValue(); bool isFirstAssociation = false; { AssociationsManager manager; //声明一个管理者,用来管理下面的AssociationsHashMap AssociationsHashMap &associations(manager.get()); if (value) { //<first, second> -> <disguised, ObjectAssociationMap{}> auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { /* it's the first association we make */ isFirstAssociation = true; } /* establish or replace the association */ auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it != associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it != refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } } // Call setHasAssociatedObjects outside the lock, since this // will call the object's _noteAssociatedObjects method if it // has one, and this may trigger +initialize which might do // arbitrary stuff, including setting more associated objects. if (isFirstAssociation) object->setHasAssociatedObjects(); // release the old value (outside of the lock). association.releaseHeldValue();}
- DisguisedPtr<objc_object> disguised{(objc_object *)object}按位取反
- ObjcAssociation association{policy, value};把外部传入的value和战略存起来
- association.acquireValue();根据上面存的policy,设置内存管理语义,copy, retain
- AssociationsManager manager;声明一个管理者,用来管理下面的AssociationsHashMap
- AssociationsHashMap &associations(manager.get());初始化HashMap
- object->setHasAssociatedObjects();与关联对象做绑定,方便开释
根据上面的代码我们就明白了关联对象是怎么存对象的了
AssociationsManager -> <disguised , ObjectAssociationMap >
ObjectAssociationMap -> <key(外部传入的), ObjcAssociation>
ObjcAssociation -> 有俩属性 外部传入的uintptr_t _policy,id _value
objc_getAssociatedObject获取关联对象实现
id_object_get_associative_reference(id object, const void *key){ ObjcAssociation association{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { ObjectAssociationMap &refs = i->second; ObjectAssociationMap::iterator j = refs.find(key); if (j != refs.end()) { association = j->second; association.retainReturnedValue(); } } } return association.autoreleaseReturnedValue();}我们探索了objc_setAssociatedObject的实现,objc_getAssociatedObject的实现就一幕了然了
通过AssociationsManager获取到AssociationsHashMap,再通过获取的AssociationsHashMap得到association。
下面我们再来看下关联对像的开释
- (void)dealloc { _objc_rootDealloc(self);}void_objc_rootDealloc(id obj){ ASSERT(obj); obj->rootDealloc();}inline voidobjc_object::rootDealloc(){ if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc &&#if ISA_HAS_CXX_DTOR_BIT !isa.has_cxx_dtor &&#else !isa.getClass(false)->hasCxxDtor() &&#endif !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); }}void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj, /*deallocating*/true); obj->clearDeallocating(); } return obj;}/************************************************************************ object_dispose* fixme* Locking: none**********************************************************************/id object_dispose(id obj){ if (!obj) return nil; objc_destructInstance(obj); free(obj); return nil;}delloac -> _objc_rootDealloc -> rootDealloc (起首判定是否有关联对象等,如果没有则直接开释)如果没有 -> object_dispose -> objc_destructInstance -> _object_remove_assocations(开释关联对象) |