类的加载

171 阅读27分钟

在之前的一篇文章map_images,load_images 分析中,我们探索和分析了map_images、load_images这两个函数,但是仍然没有对类的加载做相关详细解释,本文就探讨下类的加载。

类的加载

在前一篇文章中read_images函数中,我们说到非懒加载类的加载是通过调用realizeClassWithoutSwift来实现的,那我们就首先看下这个函数,源码如下:

realizeClassWithoutSwift

 /***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    if (!cls) return nil;
    //如果该类已经实现就直接返回
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));
    
    // fixme verify class is not in an un-dlopened part of the shared cache?
    //读取class的data()
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        //脏内存赋值
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        //这里将数据读取进来了,也赋值完毕了
        // Normal class. Allocate writeable class data.
        //申请开辟空间 --rw
        rw = objc::zalloc<class_rw_t>();
        //
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }
    
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    
#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
    
    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();
    
    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "",
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }
    
    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    //递归调用realizeClassWithoutSwift完善继承链,并处理当前类的父类、元类
    //递归实现 设置当前类、父类、元类的rw、主要目的是确定继承链(类继承链、元类继承链)
    //实现元类、父类
    //当isa找到根元类之后,根元类的isa是指向自己的,因此不会返回nil
    //如果该类已经被加载过了,则会直接返回该类,因此不会有死循环。也保证了类只加载一次
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }
        
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
    // SUPPORT_NONPOINTER_ISA
#endif
    //将父类和元类赋值给当前类,分别是isa和父类的对应值
    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);
    
    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);
    
    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }
    //双向链表指向关系,父类中可以找到子类,子类中也可以找到父类
    //通过addSubclass把当前类放到父类的子类列表中去
    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
    
    // Attach categories
    methodizeClass(cls, previously);
    
    return cls;
}

初看该函数,内容很多,但大体上可以分成以下几部分操作

  • 读取data数据,并设置ro、rw
  • 递归调用realizeClassWithoutSwift完善继承链;
  • 处理方法、属性、协议列表等;

读取data数据并设置ro、rw

我们可以通过llvm的方式来打印调试下是否真的存储了数据,首先我们准备一个类FMUserInfo,并实现load方法;

@interface FMUserInfo : NSObject
@property (strong, nonatomic) NSString * name;
@property (assign, nonatomic) int age;
- (void)instanceMetheod0;
- (void)instanceMetheod1;
@end

然后在realizeClassWithoutSwift函数体里边添加如下代码

    //FMUserInfo为我自己添加的类名
    const char * userinfoName = "FMUserInfo";
    auto fm_ro = (const class_ro_t *)cls ->data();
    //判断是否是元类
    auto fm_isMeta = fm_ro->flags & RO_META;
    if (strcmp(class_getName(cls),userinfoName) == 0 && !fm_isMeta) {
        printf("你来了");
    }

这段代码主要是为了调试使用,方便跟踪我们自己写的类初始化ro、rw使用。运行后:

image.png

我们可以看到ro的数据,是从Mach-o读取到内存时,就已经存储在bits中,通过cls->data()就可以获取到,并且这里的ro是作为一个临时变量存在的。

image.png 在这里就可以看到,一开始cls调用ro()是没有数据的,也就是ro_or_rw_ext是无值的,当继续运行:

image.png 代码走过rw->set_ro(ro)以及cls->setData(rw)之后,我们打印clsro(),此时就有了数据, image.png 在之前的文章OC类的探索-bits当中,

  • 当类第一次从磁盘加载到内存时,会在类的bits中获取到ro

image.png

  • 然后由ro来设置rw的数据,这里是通过rw->set_ro(ro)来设置rw中的ro值。 image.png
  • ro的获取,会根据情况分别取值:
    • 如果有运行时,从rw中读取
    • 如果没有运行时,从ro中读取 也就是获取当时存储在ro_or_rw_ext中的ro,也就有了数据,

image.png

获取父类、元类等,完善继承链

然后通过递归调用realizeClassWithoutSwift来获取父类与元类,在这里如果递归到之前实现的类(通过cls->isRealized()来判断),终止递归,并返回。 image.png 然后通过如下代码,对当前cls设置父类元类

//将父类和元类赋值给当前类,分别是isa和父类的对应值 
// Update superclass and metaclass in case of remapping 
cls->setSuperclass(supercls); 
cls->initClassIsa(metacls);

通过如下代码,来对父类中的子类列表添加子类

//双向链表指向关系,父类中可以找到子类,子类中也可以找到父类 
//通过addSubclass把当前类放到父类的子类列表中去 
// Connect this class to its superclass's subclass lists 
if (supercls) { 
    addSubclass(supercls, cls); 
} else { 
   addRootClass(cls); 
}

methodizeClass

realizeClassWithoutSwift函数的另一个功能就是处理方法、属性、协议列表,也就是methodizeClass函数,先看下源码:

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    ......
    //将属性列表、方法列表、协议列表添加到rw中
    //将ro中的方法列表加入到rw中
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods; //获取ro的baseMethods
    if (list) {
        //methods进行排序
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        //对rwe进行处理
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    //添加属性
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    //添加协议
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }
    //添加分类中的方法
    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

......
}

总结来看methodizeClass函数主要分如下部分:

  • 获取ro中的baseMethods,并对方法进行排序
  • 如果rwe存在,将属性列表、方法列表、协议列表等添加到rwe中 由于现阶段我们看的是非懒加载类的加载,所以这时候的rwe是没有值的(如果没有runtime的Api或者分类对方法进行改动,rwe都是没有值的),所以attachListsattachToClass这俩函数在下文分类环节中讲。

prepareMethodLists方法排序

根据断点调试,这里会调用一个prepareMethodLists方法,也就是方法排序的过程在之前OC之消息发送(objc_msgSend)一文中,在进行方法的慢查找时,是通过二分查找来进行的,但二分查找的前提是方法有序的,方法的有序排序就是在此处实现的:

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    runtimeLock.assertLocked();

    if (addedCount == 0) return;

    ......

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
    ......
}

通过prepareMethodLists函数将ro->baseMethods传入,然后再调用fixupMethodList函数进行排序。

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    //重新处理方法的sel
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
            printf("排序之前 %s --- %p",name,meth.name()); //这里添加个调试方法,方便观察
        }
    }

    // Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    // 排序方法,如果是小端模式就不需要排序
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        //这里就是排序方法
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    =============这里是为了方便方法sel打印插入的代码,非源码==================
    // Unique selectors in list.
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("排序之后 %s --- %p",name,meth.name()); //这里添加个调试方法,方便观察
    }
    ==================================================================
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

这里我添加了个调试打印的方法,来查看排序前后的方法列表。 image.png 可以明显的看到排序前方法sel的地址是无序的,在排序后方法sel的地址是从小到大的顺序排序,函数的排序是根据sel的地址来进行排序的。

懒加载类与非懒加载类

在前一篇文章map_images,load_images 分析当中说了下懒加载类非懒加载类的区别。那么懒加载类与非懒加载在加载的时候各是如何加载的呢?
初看realizeClassWithoutSwift函数会有一种熟悉的感觉,realizeClassWithoutSwift方法在之前方法的慢查找的时候也有过调用,调用路径为:

lookUpImpOrForward -> realizeAndInitializeIfNeeded_locked -> realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift 这一套流程其实也就是懒加载类在调用的时候的类的加载流程。 我们可以验证一下, 首先是非懒加载类的调用堆栈: image.png 然后是懒加载类的调用堆栈: image.png 因此,对于懒加载的就是没有实现load方法的类,会在其使用的时候才会去加载。并且不管是懒加载类或者是非懒加载类,最终都会调用realizeClassWithoutSwift来进行实现类

类加载总结

graph TB
subgraph 非懒加载类
dyld-->map_images-->readimage
end
subgraph 懒加载类
lookupImporForward-->qita[省略部分调用]-->realizeClassMaybeSwiftMaybeRelock
end
subgraph 类的加载
start[realizeClassWithoutSwift]-->cunzai[类是否存在 -cls不存在return nil]
cunzai-->already[是否已加载 -已加载return cls]
already-->rorw[读取data数据 并设置rorw]
rorw-->superandmeta[设置父类元类继承链]
superandmeta-->methodizeClass[methodizeClass处理方法 属性 协议列表]
end
realizeClassMaybeSwiftMaybeRelock.->start
readimage.->start

分类加载

在了解分类的加载,首先要知道分类的本质是什么,我们定义一个分类

//首先定义一个本类
@interface FMUserInfo : NSObject
@property (nonatomic ,copy) NSString *hobby;
-(void)printClassAllMethod:(Class)cls;
-(void)test;
@end
@implementation FMUserInfo
+(void)load {
}
-(void)test {
    NSLog(@"%s",__func__);
}
-(void)printClassAllMethod:(Class)cls{
    NSLog(@"%s",__func__);
}
@end

//此处是分类

@interface FMUserInfo (FM)
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
-(void)test;
-(void)category_instanceMethod;
+(void)categoty_classMethod;
@end

@implementation FMUserInfo (FM)
+(void)load {
}
-(void)test {
    NSLog(@"%s",__func__);
}
-(void)category_instanceMethod {
    NSLog(@"%s",__func__);
}
+(void)categoty_classMethod {
    NSLog(@"%s",__func__);
}
@end

分类的本质

然后我们通过 clang -rewrite-objc FMUserInfo+FM.m -o FMUserInfo+FM.cpp 命令来查看下底层编译。 image.png 编译后,我们看到分类的底层其实是一个_category_t的结构体,其中存储了 分类名称本类实例方法类方法协议列表属性列表。 我们看下源码中关于category_t的结构体的定义:

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> 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;
    }
};

那么FMUserInfo+FM这个分类,其底层存储了哪些内容呢,我们可以通过搜索_OBJC_$_CATEGORY_FMUserInfo_找到其底层实现: image.png 看一看到该结构体对应category_t结构体六个值,但是第二个值cls的值为0,这是为什么呢?这是因为在编译阶段,FMUserInfo+FM这个分类还没有与FMUserInfo进行关联。因此cls为0。

  • instance_methods对应_OBJC_$_CATEGORY_INSTANCE_METHODS_FMUserInfo_$_FM image.png 可以看到这里就两个实例方法testcategory_instanceMethod,格式为:sel+签名+地址,类型为method_t,这两个method_t组成method_list

  • class_methods对应_OBJC_$_CATEGORY_CLASS_METHODS_FMUserInfo_$_FM image.png class_methods这里就是类方法,包含编译后的loadcategoty_classMethod

  • properties对应_OBJC_$_PROP_LIST_FMUserInfo_$_FM

image.png 这时我们也注意到属性列表中存储了分类中的属性,但是分类中并没有对应的成员变量,而且也没有set/get方法。

那么我们对于分类中属性的设值,我们可以通过设置关联对象来实现,具体见下文【关联对象】。

总结:

  • 分类的本质是一个category_t类型的结构体
  • 结构体内有两个属性 name(类的名称)cls(类对象)
  • 有两个method_list_t类型的的方法列表,分别表示分类中实现的实例方法类方法
  • _protocol_list_t类型的是协议列表,表示分类中实现的协议
  • _prop_list_t类型的属性列表,只表示分类中定义的属性(只有定义)
  • 分类中没有成员变量,也没有set、get方法,分类中的属性都是通过关联对象来实现的。

分类的加载

还记得在上一篇map_images,load_images 分析中,有提到read_images会调用load_categories_nolock去发现加载分类,路径为map_images->_read_images->load_categories_nolock,但如果实际运行会发现这里有个判断条件didInitialAttachCategories,这个参数的初始值为false image.png 因此,分类的加载并不是在这里,但如果全局搜索load_categories_nolock,就会发现另一处调用的地方

image.png 也就是通过loadAllCategories改函数来调用,调用路径为:load_images->loadAllCategories分类实现懒加载方法的情况下
在这里loadAllCategories中根据header_info循环调用了load_categories_nolock上一篇中也讲到hi->getNext会不断的进行获取下个header_info的操作。

load_categories_nolock

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    // processCatlist 是函数的实现 这里可以看作是一个闭包
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            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.
                .....省略......
                continue;
            }
            // Process this category.
            if (cls->isStubClass()) {
               .....省略......
            } 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());
                    }
                }
            }
        }
    };
	//调用processCatlist
    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

在这一段代码中,结合上方分类的本质来看,可以看到:

  • cls为分类要关联的本类,通过remapClass(cat->cls)函数来获取,在readclass函数中,会将读取的类,通过addRemappedClass函数来添加到DenseMap表中,而此处是获取存储在表中的类来做关联。

  • 如果cls类之前没有加载,就调用addForClass函数,将分类数据添加到哈希表中去(addForClass中的get()函数就是获取到这张哈希表,在attachToClass函数那里,也是会通过get()函数再把这个表中的值取出来)。

  • 读取出来的cls,如果这个类是个非懒加载类,也就是这个类已经加载过(已经设置过rw)那么调用attachCategories,添加分类数据。

  • attachCategories通过clsflags参数区分类和元类。cats_count参数写死的是1locstamped_category_t是由lc{cat, hi}分类和header_info组成。

attachToClass

首先attachToClass并不是在load_categories_nolock函数中调用的,他的调用堆栈为realizeClassWithoutSwift->methodizeClass->attachToClass,这里说一下这个函数是因为addForClass函数,在load_categories_nolock函数中会把非懒加载分类数据添加到表中去,而attachToClass函数是把表中的分类数据添加到类中的rwe中去。

void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        //找到一个分类进来一次,即一个加载分类
        auto it = map.find(previously);
        
        if (it != map.end()) {
            //这里会走进来:挡住类没有实现load,分类开始加载,迫使主类加载,会走到if流程里面
            category_list &list = it->second;
            //判断是否是元类
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                //实例方法
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                //类方法
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                //如果不是元类,走这里
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

接下来看下如何添加分类数据:

attachCategories

static void
attachCategories(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];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    //创建rwe
    auto rwe = cls->data()->extAllocIfNeeded();
    
    //cats_count分类数量,这里写死的是1
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
	//分类中的方法,通过isMeta控制是否是分类方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
		//最大值为64,也就是说64个分类。64个分类后直接存储,之后count从0重新开始计数。
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
		//为64的时候 mcount 初始化为0
                mcount = 0;
            }
	   //mcount在这里变化 mlists中从后往前存分类方法列表,也就是后加载的分类在前面。
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
	
	//属性的处理
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
	//协议的处理
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
	//排序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
	//将所有分类数据存储。超过64个后会清0。相当于再多了一次结构。二层结构了。由于是从后往前存的,所以将前面空白的区域剔除。
        //mlists + ATTACH_BUFSIZ - mcount 是一个二维指针
        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函数首先通过extAllocIfNeeded创建了rwe数据。
  • 在非懒加载类调用流程中,也就是通过load_categories_nolock函数调用attachCategories函数,cats_count传值为1,所以这里相当于没有循环。通过methodsForMeta获取分类方法列表。
  • ATTACH_BUFSIZ的值为64,当mcount64的时候,重新开始计数。也就是说当cats_count > 64的时候会重新进行计数。但是目前loadAllCategories传递的是1所以不会进入这里的逻辑。那么只有attachToClass会进入这个逻辑了。
  • 之后调用prepareMethodLists进行排序,然后会调用attachLists将分类数据加入rwe中。

extAllocIfNeeded

class_rw_ext_t *extAllocIfNeeded() {
   //获取rwe
   auto v = get_ro_or_rwe();
   if (fastpath(v.is<class_rw_ext_t *>())) {
         //如果之前创建了直接返回
         return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
   } else {
         //创建rwe
         return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
   }
}
    
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    //调用alloc创建空间
    auto rwe = objc::zalloc<class_rw_ext_t>();
    //设置版本,元类为7,非元类为0。
    rwe->version = (ro->flags & RO_META) ? 7 : 0;
    //获取ro中的方法列表
    method_list_t *list = ro->baseMethods;
    if (list) {
        //是否深拷贝,跟踪的流程中 deepCopy 为false
        if (deepCopy) list = list->duplicate();
        //将ro的方法列表放入rwe中。
        rwe->methods.attachLists(&list, 1);
    }

    //属性
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    //协议
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    //设置rwe,rwe-ro = ro
    set_ro_or_rwe(rwe, ro);
    return rwe;
}
  • extAllocIfNeeded函数内部调用是extAlloc创建rwe,如果之前创建过直接返回
  • romethods数据拷贝到rwe中(这里没有深拷贝,通过extAllocIfNeeded函数调用的都是浅拷贝)。
  • 链接属性和协议。
  • 设置rwerwe->ro = ro也就是rwe中的ro指向ro

methodsForMeta

method_list_t *methodsForMeta(bool isMeta) {
    if (isMeta) return classMethods;
    else return instanceMethods;
}

判断是否元类,元类返回classMethods,类返回instanceMethods,对应category_t结构体中的类方法列表实例方法列表

attachLists

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
//            计算之前的旧list的大小
            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;
            //之前的数据从addedCount数组下标开始,存放旧的list
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            //新数据从0开始放入
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            //释放就数据
            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;
            //有旧列表,oldCount为1否则为0
            uint32_t oldCount = oldList ? 1 : 0;
            //新count为oldCount+addedCount,也就是计算新的容量和
            uint32_t newCount = oldCount + addedCount;
            //开辟新空间,设置新数组,类型为array_t
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            //设置数组大小
            array()->count = newCount;
            //判断oldlist是否存在,到这里的old肯定是存在的,将旧的list放到数组的末尾
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                //新的数组从0开始添加
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

根据注释我们可以对attachLists做个总结: attachLists方法主要是将类和分类的数据进行合并;

  • 首先加载本类的数据,如果此时本类数据为空,也就是list为空,那么分类数据addedLists就是list,也就是0对1的流程
  • 如果本类中有数据或者list有数据(加载过一个分类),那么就是1对多,如下图所示 image.png
  • 如果之前已经有很多list数据(加载过多个分类),那么就是多对多 image.png

源码总结:

至此通过类的加载与分类的加载源码分析,我们可以看到类与分类加载交汇的地方attachCategories,根据交汇的地方我们可以大致推测类与分类加载的流程

  • 如果本类是非懒加载类,无分类,那么加载流程为map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass,但此时分类无数据,只创建rw
  • 本类是非懒加载类,分类也是非懒加载类,也就是cls->isRealized为true,本类创建完后,调用堆栈如下:load_images->loadAllCategories->load_categories_nolock->attachCategories(创建rwe)->attachLists
  • 本类是懒加载类,分类中有多个非懒加载的,则会在load_images函数中获取mhdr,并获取NonlazyCategoryList然后再调用realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories 也就是如下图所示: image.png

探究分类加载的流程

准备

我们准备个FMUserInfo的本类以及FMUserInfo (FM)的分类

@interface FMUserInfo : NSObject
@property (nonatomic ,copy) NSString *hobby;
-(void)printClassAllMethod:(Class)cls;
-(void)test;
@end
@implementation FMUserInfo
+(void)load {
}
-(void)test {
    NSLog(@"%s",__func__);
}
#pragma mark - 打印当前类的MethodList
-(void)printClassAllMethod:(Class)cls{
    NSLog(@"%s",__func__);
}
@end
//======
@interface FMUserInfo (FM)
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
-(void)test;
-(void)category_instanceMethod;
+(void)categoty_classMethod;
@end
@implementation FMUserInfo (FM)
+(void)load {
}
-(void)test {
    NSLog(@"%s",__func__);
}
-(void)category_instanceMethod {
    NSLog(@"%s",__func__);
}
+(void)categoty_classMethod {
    NSLog(@"%s",__func__);
}
@end  

为了方便跟踪,我们在主要的方法里边添加如下代码,这样就可以针对要研究的类进行方法跟踪以及堆栈调用

const char *mangledNamecus = cls->nonlazyMangledName();
const char *personName = "FMUserInfo";
auto lg_ro = (const class_ro_t *)cls->data();
auto lg_isMeta = lg_ro->flags & RO_META;
if (strcmp(mangledNamecus, personName) == 0 && !lg_isMeta) {
     printf("%s:--->FMUserInfo来到了这里\n",__func__);
}

通过类的加载与分类的加载源码分析,我们可以看到类与分类的加载大致分为以下六种情况:

类+分类分类+实现load分类+未实现load多个分类
类+实现load非懒加载类+非懒加载分类非懒加载类+懒加载分类非懒加载类+多个分类
类+未实现load懒加载类+非懒加载分类懒加载类+懒加载分类懒加载类+多个分类

本类非懒加载,分类非懒加载

image.png 本类加载流程为:map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass 分类加载流程为:load_images->loadAllCategories->load_categories_nolock->attachCategories(创建rwe)->attachLists

本类非懒加载,分类懒加载

image.png 可以看到这一过程就是非懒加载本类的加载过程,那么分类是何时加载的呢? 如果我们在methodizeClass函数中添加如下代码,来打印下ro中的函数

{
      printf("ro->baseMethods中的方法打印:\n");
      method_list_t **addedLists = &list;
      method_list_t *mlist = addedLists[0];
      for (auto& meth : *mlist) {
           const char *name = sel_cname(meth.name());
           printf("%s\n",name);
      }
      printf("方法打印结束\n");
}

image.png 这时就会看到当走到methodizeClass函数中时ro->baseMethods函数中就已经含有分类中的方法了。

本类懒加载,分类懒加载

image.png 可以看到其加载是在main函数之后,通过lookUpImpOrForward->realizeClassMaybeSwiftAndUnlock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift->methodizeClass方式调用,并且在methodizeClass函数中ro的数据中已经有分类的数据。

本类懒加载,分类非懒加载

image.png 这一过程与本类非懒加载,分类懒加载一致

多分类情况

我这里是添加了FMUserInfo(FMOne)以及FMUserInfo(FMTwo),并分别添加了one()、two()实例方法。

  • 当本类懒加载,且有多个非懒加载分类的时候: image.png 可以看到,分类也是在main函数之前调用,其完整的调用栈为:load_images->loadAllCategories->load_categories_nolock->prepare_load_methods->_getObjc2NonlazyCategoryList->realizeClassWithoutSwift->methodizeClass->attachCategories
  • 当本类懒加载,分类也有懒加载且只有一个非懒加载分类的时候: image.png ro中包含有分类的方法。过程与本类非懒加载,分类懒加载一致
  • 当本类懒加载,分类也有懒加载且也有多个非懒加载分类的时候: image.png
  • 当本类非懒加载,且有一个或多个非懒加载分类的时候: image.png 这一过程与本类非懒加载,分类非懒加载一致
  • 当本类非懒加载,但有多个懒加载分类的时候:

image.png 过程与本类非懒加载,分类懒加载一致

分类加载总结:

本类加载时机分类加载时机存储位置(ro/rwe)
本类非懒加载,分类非懒加载map_imagesloadAllCategories->attachCategoriesrwe
本类非懒加载,分类懒加载map_images编译ro
本类懒加载,分类懒加载第一次调用编译ro
本类懒加载,分类非懒加载map_images编译ro
本类懒加载,分类非懒加载 > 1prepare_load_methods->realizeClassWithoutSwiftattachToClass->attachCategoriesrwe
本类懒加载,分类懒=N&分类非懒=1map_images编译ro
本类非懒加载,分类非懒加载>0map_imagesloadAllCategories->attachCategoriesrwe
本类非懒加载,分类懒>0map_images编译ro

类扩展

类扩展

  • 可以说成是特殊的分类,也称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法 那么类扩展与分类的区别呢:
    分类
  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员变量,也无法取到
  • 注意:其实可以通过runtime给分类添加属性
  • 分类中用@property定义变量,只会生成变量的getter、setter方法的声明,不能生成方法的实现和带下划线的成员变量。

类扩展底层

首先我们先定义个类扩展的类:

@interface FMPerson : NSObject
@property (strong, nonatomic) NSString * name;
- (void)instanceMetheod;
@end
@interface FMPerson()
@property (copy, nonatomic)NSString  * ex_name;
- (void)ex_instanceMetheod;
@end
@implementation FMPerson
- (void)instanceMetheod{
    NSLog(@"-->%s",__func__);
}
- (void)ex_instanceMetheod{
    NSLog(@"-->%s",__func__);
}
+(void)load{
    NSLog(@"-->%s",__func__);
}

然后通过clang -rewrite-objc main.m -o main.cpp 命令来查看下底层编译。 image.png 可以看到在编译阶段类扩展中的数据就已经添加到了成员变量以及方法列表当中了,而且也自动生成了set/get方法。
当我们在加载类的时候在realizeClassWithoutSwift函数中初始化rorw的时候,从cls ->data()中读取到的数据中就已经将类扩展中的数据添加到方法列表及成员变量中了。 image.png

关联对象

在【分类的本质】中提到,分类中的属性添加后不能生成set/get方法,也不会在类中存储成员变量的信息,所以分类中的属性不能直接赋值,需要通过关联对象的方式来实现。如下方代码:

@interface FMUserInfo (FM)
@property (nonatomic ,copy) NSString *name;
-(void)testFM;
@end
@implementation FMUserInfo (FM)
+(void)load {
}
-(void)testFM {
    NSLog(@"%s",__func__);
}
static const char *FMNameKey = "FMNameKey";
-(void)setName:(NSString *)name {
    objc_setAssociatedObject(self, FMNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name {
    return objc_getAssociatedObject(self, FMNameKey);
}
@end

添加关联的函数主要为以下几个函数:

//为给定的对象设置一个关联值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//返回给定对象关联的值
id objc_getAssociatedObject(id object, const void *key)
//移除给定对象的所有关联
void objc_removeAssociatedObjects(id object)

下文也主要以这三个函数展开:

objc_setAssociatedObject

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

  • object 要关联的对象,实例对象。
  • key 对应的key,标识符,方便查找。
  • value 属性的值,传递nil来清除现有关联。。
  • policy 属性的关联策略。 属性的关联策略一般有以下几种:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    //指定关联对象的弱引用
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    //指定对关联对象的强引用。不可以进行原子操作
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    // 指定复制关联对象。关联不是原子的
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    // 指定对关联对象的强引用。关联是原子的
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    // 指定复制关联对象。关联是原子的。
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

查看源码发现objc_setAssociatedObject函数其实调用的是_object_set_associative_reference函数。

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}
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));
    //对关联对象 object 的指针包装成DisguisedPtr (对objc需要关联的对象进行封装,封装成统一的格式)
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 把关联策略policy和关联属性的值保存到 ObjcAssociation的实例里
    ObjcAssociation association{policy, value};
    // 处理关联策略 根据policy策略去判断是进去retain还是copy操作
    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        //相当于加锁和解锁,锁的范围是manager的生命周期
        //AssociationsManager的构造函数以及析构函数 C++代码
        AssociationsManager manager;
        
        /**
         //AssociationsHashMap为DenseMap哈希表,存储DisguisedPtr(object)和ObjectAssociationMap
         
         //ObjectAssociationMap也是哈希表 const void *为传入的key值 ObjcAssociation为关联策略
         
         typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
         
         typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
         */
        //全局唯一的一张表
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //如果value有值,更新哈希表中的值
            //表里边存储disguised,和关联策略表
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                //表示第一次关联该对象
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            //AssociationsHashMap更新这个表里边的数据
            /* 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 {
            //AssociationsHashMap 查找 disguised 对应的ObjectAssociationMap
            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);
                    //如果没有值,则擦除ObjectAssociationMap
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    //第一次关联通过setHasAssociatedObjects方法`标记对象存在关联对象`设置`isa指针`的`has_assoc`属性为`true`
    if (isFirstAssociation)
        object->setHasAssociatedObjects();
    //释放association中的旧的值
    // release the old value (outside of the lock).
    association.releaseHeldValue();
}
  • DisguisedPtr封装object,也就是关联对象
  • ObjcAssociation封装关联策略和值
  • acquireValue处理关联策略 根据policy策略去判断是进去retain还是copy操作,相当于加锁和解锁,锁的范围是manager的生命周期。
  • AssociationsManager是一个构造函数以及析构函数 代码为:
class AssociationsManager {
...
AssociationsManager()   { AssociationsManagerLock.lock(); }
 ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

}
...

这里我们可以自己写一个这种函数验证下

struct FMObjc {
    FMObjc() { printf("%s \n",__func__); }
    ~FMObjc() { printf("%s \n",__func__); }
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"作用域外");
        {
            NSLog(@"作用域开始");
            FMObjc objc;
            NSLog(@"作用域结束");
        }
        NSLog(@"作用域外");
    }
    return 0;
}

image.png

  • AssociationsHashMap 是一个DenseMap类型的哈希表,存储DisguisedPtr(对object封装的类型)和ObjectAssociationMap 定义如下:
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
  • ObjectAssociationMap也是哈希表 const void *为传入的key值 ObjcAssociation关联策略和关联属性值 根据其结构以及调用,大致推测AssociationsHashMap 哈希表为如下结构。

image.png

  • manager.get()为获取全局静态哈希表
  • try_emplace在哈希表中查找并返回迭代器数据,如果该表没有则创建.
template <typename... Ts>
  std::pair<iterator, bool> try_emplace(KeyT &&Key, Ts &&... Args) {
    BucketT *TheBucket;
    //根据key去查找对应的Bucket
    if (LookupBucketFor(Key, TheBucket))
      ////通过make_pair生成相应的键值对
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    //如果没有查询到 将数据插入bucket中,返回bucket
    TheBucket =
        InsertIntoBucket(TheBucket, std::move(Key), std::forward<Ts>(Args)...);
    //通过make_pair生成相应的键值对
         //true表示第一次往哈希关联表中添加bucket
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

在一开始进入的时候会先进行查找,如果找到就直接返回,找不到就进行插入数据操作,然后包装数据返回。 image.png LookupBucketFor(Key, TheBucket)通过key找一下AssociationsHashMap是否存在TheBucket,如果找到就返回用make_pair包装的TheBucket

image.png 找不到的时候可以看到做了insert操作,并把插入后的数据做了返回。

  • object->setHasAssociatedObjects()通过setHasAssociatedObjects方法标记对象存在关联对象也就是设置isa指针has_assoc属性为true

这部分就是在有值的的情况下设置值的流程,总结一下:

设置值流程总结

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 判断是否插入的关联值是否存在:
    • 3.1: 存在走第4步
    • 3.2: 不存在就走 : 关联对象插入空流程
  • 4: 创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 5: 如果发现没有这个 key 就插入一个 空的 BucketT进去 返回
  • 6: 标记对象存在关联对象
  • 7: 用当前 修饰策略 和 值 组成了一个 ObjcAssociation 替换原来 BucketT 中的值
  • 8: 标记一下 ObjectAssociationMap 的第一次为 false

objc_getAssociatedObject

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);

}
id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
	//获取AssociationsHashMap表
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
	   // 取出ObjectAssociationMap
            ObjectAssociationMap &refs = i->second;
	   // 使用参数 key,取出ObjectAssociation
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
	       // ObjectAssociation包含要取出的 value 和 policy。
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }
	//将关联值交由自动释放池来管理
    return association.autoreleaseReturnedValue();
}

取值流程总结

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 4: 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
  • 5: 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 6: 返回_value

关联对象销毁

前边两个看了关联对象的创建、设置、获取,那么关联对象如何销毁呢? 一个对象的销毁我们通常调用dealloc函数,而dealloc函数也有对于关联对象的处理:

dealloc->_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance->_object_remove_assocations

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa = obj->getIsa();

        if (isa->hasCxxDtor()) {
            object_cxxDestruct(obj);
        }

        if (isa->instancesHaveAssociatedObjects()) {
            _object_remove_assocations(obj);
        }

        objc_clear_deallocating(obj);
    }

    return obj;
}

在对象的isa指针中,我们知道has_assoc为关联对象标志位。0:不存在,1:存在, 在关联对象的设置那里,如果关联对象设置成功,则会调用setHasAssociatedObjects函数来设置对象的has_assoc参数,而在dealloc过程中,就会调用isa->instancesHaveAssociatedObjects()函数判断是否存在关联对象,如果存在直接调用_object_remove_assocations进行移除关联对象。

也就是说关联对象是不需要去手动释放的。