iOS手把手带你探索Category

开发者 2024-9-12 03:27:21 32 0 来自 中国
在我们的现实开发中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(开释关联对象)
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 20:27, Processed in 0.185570 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表