iOS——关联对象学习补充

分类

  • 在分类中添加属性会生成对应的成员变量,会生成对应的setter和getter方法的声明,但是不会生成setter和getter方法的实现。
  • 分类中的可以写@property,会编译通过,但是引用变量会报错。
  • 分类中可以/只能访问原有类中.h中的属性。如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问。
  • 在本类和分类有同名方法时,优先调用分类的方法。同名方法调用的优先级为分类 > 本类 > 父类。
  • 如果多个分类中都有和原有类中同名的方法,那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
  • 因为分类是会被子类继承的,所以只对NSObjec添加分类,那么所有的OC类均可以调用我们的扩展方法!
  • 对于分类中声明的方法,我们可以不去实现,可以交由子类在需要时进行实现,这点和协议很像。区别是协议是<协议名>,而分类是(分类名)
  • OC分类属于Runtime运行时特性,是OC语言独有的创新,其他编程语言所不具备这样的特性! 类扩展属于编译器特性,在编译阶段就会被添加合并到原类中!
  • 分类不区分类方法与实例方法,本质类加载过程分类属于插入数据,实例方法插入到类中,类方法插入到元类中,所以自己并不会有分元类。

分类方法不会覆盖掉原来类中的方法,而是共存的。但是分类中的方法在前面,原来的类中的方法在后面,调用的时候,就会调用分类中的方法,如果多个分类有同样的方法,后编译的分类会调用。

分类中不能直接添加成员变量

会直接报错Instance variables may not be placed in categories,成员变量不能定义在分类中
这是因为在分类的源码中:

struct category_t {
    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;
    // 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);
};

没有存放成员变量的数组,只有属性、协议等

类的加载过程(类的实现)

_objc_init

我们根据之前学的dyld的加载可知:类的加载是在程序启动过程中由dyld动态链接器完成的,在 dyld 完成镜像加载和初始化后,objc_init 会被调用来初始化runtime环境。此时,类和类的方法会被注册到运行时系统中。

void _objc_init(void)
{
    // 保证初始化方法只执行一次
    static bool initialized = false;

    if (initialized) return;

    initialized = true;


    // fixme defer initialization until an objc-using image is found?
    // 读取影响运行时的环境变量,例如,我们可以根据这个方法打印出那些关键key
    environ_init();
    // 关于线程key的锁定,tls(thread_local_storage)。例如,每个线程数据的析构函数
    tls_init();
    // 运行c++静态构造函数。在dyld调用之前,objc自己调用其构造函数
    static_init();
    // 运行时环境初始化,主要是建表
    runtime_init();
    // 异常处理初始化
    exception_init();

#if __OBJC2__
    // 缓存条件初始化
    cache_t::init();

#endif
    // 启动回调机制,通常不做什么,因为所有初始化都是惰性的
    // 但对于某些进程,我们要立马加载trampoline dylib
    _imp_implementationWithBlock_init();
    // 注册dyld与objc的通知,&map_images,加一个&符号是代表同步进行
    // map_images是很耗时的操作,而映射镜像是很重要的必须保证dyld与objc的函数指针同步
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);


#if __OBJC2__

    didCallDyldNotifyRegister = true;

#endif

}

在这个函数中调用的 runtime_init();:

void runtime_init(void)
{
    // 创建分类的表
    objc::unattachedCategories.init(32);
    // 创建已分配的类、元类的表
    objc::allocatedClasses.init();
}
  • 主要是运行时的初始化,开辟类、分类的表空间
  • 主要分为两部分:分类初始化、类的表初始化

_dyld_objc_notify_register

这个在刚刚的objc_init中也有见过,它里面的函数是数据的加载的重点:

  • map_images:映射镜像,将image镜像文件加载进内存时,会触发该函数,在这个过程中会加载类、分类、协议等数据
  • load_images:加载镜像,dyld初始化image会触发该函数,load方法就会在此执行
  • unmap_image:dyld将image移除时会触发该函数

map_images

其源码只调用了map_images_nolock函数。
map_images_nolock 源码调用_read_images开始读取镜像文件

_read_images(读取镜像文件)

该函数才是map_images流程的核心开始。很重要了,它做了很多事情,去读取mach-o,以header开始进行读取。你可以直接理解为读取镜像文件到内存中去。它对类的处理大致为以下流程:
第一次进来的时候去创建一个类表,该类表包含了类名等(不是runtime的,是全局的已经被加载或没被加载的类)。先将所有的类都加到这个大类表中,并且将类的地址和名字进行绑定,也就是给类赋予名字。然后将混乱错误类处理(就是我们删除的类,但是删除后内存中依旧有这块地址没被清除,我们称为futureClass。)
然后修复一些重映射类,就是重映射没被加载进来的,然后对分类进行处理,但是在启动的时候并不会走,也就是说分类加载并不在这时候。
最后对非懒加载类进行处理,里面包含了类的实现。

功能:
该函数总有这么几大块功能:

  1. 修复selector引用
  2. 类的处理
  3. 协议处理
  4. 分类的处理
  5. 非懒加载类的实现(需要设置非懒加载的时候才会进入)
    我们在这里主要分析类的处理和非懒加载类的实现。
    在类的底层分析中我们得知,类的数据存储在rw中,但是我们从内存获取到的数据是ro,所以需要重新构造一下,将ro数据加到rw和rwe中

rw, ro, rwe回顾
这三个结构体是影响类的核心所在。
rw本质class_rw_t,dirtyMemory,它包含了ro与rwe,也就是说,我们平时所用到的方法,协议,属性等都是从这里取出来的,类实现的时候默认是ro数据,要记得rw开辟的时候赋值操作。但是为什么说包含rwe呢?
ro本质class_ro_t,cleanMemory,这是类的干净的东西,其编译时就能确定的,例如类名,成员变量,乃至一些方法、属性等,这些存在磁盘中,所以我们看上面篇章的时候类实现都是先从ro入手就是因为这些不可变,存在磁盘。
rwe本质class_rw_ext_t相当于rw的扩展,因为运行时机制我们可以动态添加方法、属性、协议等以及分类操作都会影响类的结构也就是rw,所以在有这些操作的时候系统就会进行开辟rwe,进行创建。但是又不是每一个类都会有上面操作,所以这些类就不会去开辟rwe,避免去染指类的结构

readClass(读取类信息)

在阅读了前面_read_image的源码后,我们发现它调用了readClass函数。

_read_image调用readClass函数做了两件事:

  • 使用addNameClass函数给类映射类名
  • 使用addClassTableEntry函数添加类和元类到所有类的动态表上

那么readClass中是如何实现的呢?
实际上readClass完成这个任务是通过:**一个是将类名与类映射,一个是将类的地址添加到所有类的动态表中 **
源码:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 非懒加载类获取类名的方式,但是其实这里已经都能获取了,可以看里面实现方式是从ro读取的
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();
    
    Class replacing = nil;
    if (mangledName != nullptr) {
    // 在这里千万不要以为类实现中的ro赋值于rw的操作是在这。
    // 可以自行验证,你会发现无论是系统还是自己创建的都没有走这里
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.

            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            // 将类地址与名字绑定起来
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        // 将绑定后的类加到全局大类表中
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    return cls;
}
  • addNamedClass用来给类添加类名
  • addClassTableEntry用来添加类或元类到表上
addNamedClass(添加类名)

通过NXMapInsert函数将类名映射到类上,类和类名的映射是通过一个表来关联的。以后可以更方便的通过类名获取到类

/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
 给一个需要命名的非元类添加映射关系:name=>cls
 如果出现重复的类名,则发出警告,并且使用旧的映射关系
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        //在这里将类名添加到类的映射中
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // ASSERT(!cls->isRealized());
}
addClassTableEntry(添加到类动态表)

有一个所有类的动态表,这个表包含了所有的类,现在就将这个类添加到表中,如果是元类也要把元类添加进去
添加类的地址到一个表中,此处也是为了更好的查找类,以后可以直接通过类地址查找类
这里添加的表是之前在objc_init中allocatedClasses开辟的空间。

/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
 将一个类cls添加到所有类的表中
 如果addMeta为true,则自动的将其元类也添加进去
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    /*
     这个类可以通过共享的内存或数据段成为一个已知的类,但是不可以因为早已加入到动态列表而称为已知类
     当然这是因为我们此时要加入,所以也就不能在动态列表中存在了。
     */
    /*
     得到之前分配的动态列表(在objc_init中分配的)
     */
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    //如果不是一个已知类,也就是表中不存在cls,则将其加入
    if (!isKnownClass(cls))
        set.insert(cls);
    //再继续添加其元类
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

realizeClassWithoutSwift(类列表数据的构造过程)

上面只是将类的名称和地址添加到表中,并没有看到rwe的方法列表、协议列表、属性列表、变量列表的构造过程,现在看下它是什么时候构造的。

realizeClassWithoutSwift方法就是对于类实现的核心方法了,任何类(懒加载类,非懒加载类)都要经过这个方法的洗礼,才能实现。
其中最关键的就是对rw的赋值等等,以及本类实现,连带着元类,父类,父类元类等都实现

代码结构: 1、设置rw 2、递归实现父类和元类的继承链 3、附着分类数据

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    
    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    // 如果类已经实现就返回,证明流程错误,里面有个方法会进行assert
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    //将类的data()从ro变为rw--------------------------------
    auto ro = (const class_ro_t *)cls->data();
    // 看看是不是元类
    auto isMeta = ro->flags & RO_META;    
    // 如果是futer class,就是在read_images方法的时候那些混乱类
    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>();
        // ro 赋值于rw真正地方
        rw->set_ro(ro);
        // 标识类实现
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        // 不用说也知道什么意思
        cls->setData(rw);
    }
    //rw里保存的就是数据信息
    //ro是保存在rw中的,所以需要赋值给rw
    //先将信息从类中获取到,这个类就是macho中读取的类,此时虽然有data(),但这是从MachO中获取的原始数据
    //原始数据其实就是ro,这是类本身的数据,也就是干净内存,所以可以通过强转为class_ro_t
//--------------------------------------------------------
    // 这是类的缓存初始化,就是消息查找的时候那个cache_t
    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.
    // 递归实现父类和元类的继承链rw------------------------------
    // 去实现它的父类
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    // 去实现它的元类
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
//----------------------------------------------------------
// nonpointer_isa,不用关心
#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.
        // 我们看到元类的isa打印是其类的名字,因为这里
        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;
        //如果说你记得上一章我说的环境变量,OBJC_DISABLE_NONPOINTER_ISA
        // 所以nonpointer_isa标识就是末尾为1,下面只是去设置
        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

    // Update superclass and metaclass in case of remapping
    // 设置其父类,以及isa指向元类
    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;
    }

    // Connect this class to its superclass's subclass lists
    // 添加关系,如果有父类
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    //附着类别数据rw-----------------------------------------
    // 不要关心参数previously,其实它可以当做是备用参数,对我们来讲无实际意义
    methodizeClass(cls, previously);
    //对类的rwe进行设置
    //将分类信息添加到rwe中
    //肃然这里注释写的是附着分类数据,其实rwe中也会把类的数据也就是ro添加到rwe中
//----------------------------------------------------------

    return cls;
}

小结:

  • 加载到内存中的原始的类的数据格式并不是我们所需要的格式,所以就需要重新设置,其实设置的就是rw
  • 类的实现其实就是将类的bits中的data进行重新赋值,基本上就是给rw进行赋值,包括ro和rwe
  • 而这个data()就是rw,rw包含ro和rwe
  • ro是类本身的数据,包括方法列表、属性列表、协议列表、成员变量列表
  • rwe是分类以及运行时改变的数据,包括方法列表、属性列表、协议列表,还有ro

methodizeClass(附着分类、修复类)

代码结构:1、方法排序;2、添加ro数据到rwe中;3、附着分类数据到rwe中

  • 先获取到ro的基本方法列表
  • 通过prepareMethodLists进行排序,这样方法查找时可以用二分查找法来查找imp
  • 通过attachLists将ro的数据附着到rwe上
  • 属性和协议列表也通过attachLists附着到rwe上
  • 上面是把ro的列表加到了rwe中,还需要通过objc::unattachedCategories.attachToClass将分类的方法列表加到rwe中
  • 列表有三种需要操作的,方法列表、协议列表、属性列表(变量列表不存放到rwe中,所以不需要进行操作)

attachToClass(获取分类附着到类上)

将分类或运行时创建的数据添加到类的rwe上

  • 这里通过get()拿到的是分类中的数据
  • load_images函数将分类数据加载后存放到数组中,在类进行加载时直接拿过来附着
  • 这里调用attachCategories函数传入的分类是这个类当前可以加载的所有分类。

总结

在这里插入图片描述

  • 类加载到内存中,需要重新构造类,构造的就是rw,rw包括rwe和ro
  • 类的处理包括给类映射类名、将类和元类添加到所有类的表中
  • rw的构造过程就是给rw添加ro,并且生成rwe
  • rwe的生成过程是先添加ro的数据,之后获取到分类数据,添加到rwe中
  • ro以及分类中的方法列表均要按照方法选择地址排序,方便后续的二分查找法
  • 在将方法列表添加到类的rwe时,需要倒序插入。后加载分类的在前面,后加载的分类在后面,类的数据在最后。

load的加载过程

类load表:loadable_class

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

分类load表:loadable_category

struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};

load_images(加载镜像)

该方法做了两件事,一个是分类的加载,一个是load的加载和调用

void load_images(const char *path __unused, const struct mach_header *mh)
{
    // 当完成dyld通知注册回调后,这里看函数名意思是加载所有的分类,但是是这样吗?
    // 
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // 去准备分类方法,看备注就知道
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // 这个没什么看的,就是去调用load方法了,但是先调用类的,再按顺序调用分类的
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}


在这里插入图片描述

分类的加载过程

分类的加载方法load_categories_nolock

static void load_categories_nolock(header_info *hi) {
    // 判断是否存在预优化的类别
    bool hasPreoptimizedCategories = hi->info()->dyldCategoriesOptimized() && !DisablePreattachedCategories;
    // 判断是否存在被覆盖的共享缓存镜像
    bool hasRoot = dyld_shared_cache_some_image_overridden();
    // 判断类别是否包含类属性
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;

    // 定义处理类别列表的lambda表达式
    auto processCatlist = [&](category_t * const *catlist, bool stubCategories) {
        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) {
                // 类别的目标类丢失(可能是弱链接),忽略该类别
                if (PrintConnecting) {
                    _objc_inform("CLASS: 忽略类别 \?\?\?(%s) %p 目标类丢失", cat->name, cat);
                }
                continue;
            }

            // 处理当前类别
            if (cls->isStubClass()) {
                // 如果是Stub类,类别添加到未关联的类别列表中
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // 将类别注册到目标类中
                if (!didInitialAttachCategories && hasPreoptimizedCategories && 
                    objc::inSharedCache((uintptr_t)cls) && 
                    objc::inSharedCache((uintptr_t)cls->safe_ro()))
                    continue;
                 //将分类附加到一个类或其元类时所采取的步骤。
                if (cat->instanceMethods ||  cat->protocols ||  cat->instanceProperties) {
                    if (cls->isRealized()) {
                        if (slowpath(PrintConnecting))
                            _objc_inform("CLASS: 附加类别 (%s) %p 到类 %s", cat->name, cat, cls->nameForLogging());
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        if (slowpath(PrintConnecting))
                            _objc_inform("CLASS: 添加未关联的类别 (%s) %p 到类 %s", cat->name, cat, cls->nameForLogging());
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols ||  (hasClassProperties && cat->_classProperties)) {
                    if (cls->ISA()->isRealized()) {
                        if (slowpath(PrintConnecting))
                            _objc_inform("CLASS: 附加类别 (%s) %p 到元类 %s", cat->name, cat, cls->nameForLogging());
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        if (slowpath(PrintConnecting))
                            _objc_inform("CLASS: 添加未关联的类别 (%s) %p 到元类 %s", cat->name, cat, cls->nameForLogging());
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    // 判断是否需要扫描类别列表
    bool scanCatlist = didInitialAttachCategories || !hasPreoptimizedCategories || hasRoot;
    if (scanCatlist) {
        if (slowpath(PrintPreopt)) {
            _objc_inform("预优化:扫描镜像 %p %s 中的类别 "
                         "- didInitialAttachCategories=%d "
                         "hi->info()->dyldCategoriesOptimized()=%d hasRoot=%d",
                         hi->mhdr(), hi->fname(), didInitialAttachCategories,
                         hi->info()->dyldCategoriesOptimized(), hasRoot);
        }
        processCatlist(hi->catlist(&count), /*stubCategories*/false);
    } else {
        if (slowpath(PrintPreopt)) {
            _objc_inform("预优化:忽略镜像 %p %s 中的预优化类别", hi->mhdr(), hi->fname());
        }
    }

    // 无论如何都要扫描指向stub类的类别列表,因为这些类别不是预优化的
    processCatlist(hi->catlist2(&count), /*stubCategories*/true);
}

这段代码首先判断了是否存在经过dyld(动态链接器)优化的类别、共享缓存镜像中是否有被覆盖的类、类别是否包含类属性
然后定义Lambda函数用于处理类别列表,遍历类别链表,如果类已实现,则使用attachCategories将分类附加到这个类上,同时按需要重建类的方法列表、属性列表等。
如果目标类还没有实现,无法直接附加分类。这时,将这个分类信息保存到 objc::unattachedCategories 中,以便在类实现之后再附加。

从上面的代码我们可以知道:分类的加载的核心代码是attachCategoriesattachToClass

attachCategories

attachCategories的作用是将分类的方法、属性和协议附加到目标类或元类上:

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    // 如果开启了 PrintReplacedMethods 选项,则打印替换的分类信息
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }

    // 如果开启了 PrintConnecting 选项,则打印附加分类的信息
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
        for (uint32_t i = 0; i < cats_count; i++)
            _objc_inform("    category: (%s) %p", cats_list[i].cat->name, cats_list[i].cat);
    }

    /*
     * 在启动期间,只有少数类有超过 64 个分类。
     * 这使用了少量的栈空间,并避免了 malloc。
     *
     * 分类必须以正确的顺序添加,即从后到前。
     * 为了做到这一点,我们从前到后迭代 cats_list,构建局部缓冲区,然后
     * 在 chunks 上调用 attachLists。attachLists 会将列表添加到前面,因此最终结果
     * 是预期的顺序。
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;  // 定义缓冲区大小
    struct Lists {
        ReversedFixedSizeArray<method_list_t *, ATTACH_BUFSIZ> methods;    // 存储方法列表
        ReversedFixedSizeArray<property_list_t *, ATTACH_BUFSIZ> properties;  // 存储属性列表
        ReversedFixedSizeArray<protocol_list_t *, ATTACH_BUFSIZ> protocols;  // 存储协议列表
    };
    Lists preattachedLists;  // 预附加分类的列表
    Lists normalLists;  // 正常分类的列表

    bool fromBundle = NO;  // 标记是否来自 bundle
    bool isMeta = (flags & ATTACH_METACLASS);  // 标记是否为元类
    auto rwe = cls->data()->extAllocIfNeeded();  // 获取类的扩展数据

    // 遍历分类列表
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);  // 获取分类的实例方法列表或类方法列表
        bool isPreattached = entry.hi->info()->dyldCategoriesOptimized() && !DisablePreattachedCategories;  // 判断是否为预附加分类
        Lists *lists = isPreattached ? &preattachedLists : &normalLists;  // 根据是否为预附加分类选择列表

        if (mlist) {
            if (lists->methods.isFull()) {
                prepareMethodLists(cls, lists->methods.array, lists->methods.count, NO, fromBundle, __func__);  // 准备方法列表
                rwe->methods.attachLists(lists->methods.array, lists->methods.count, isPreattached, PrintPreopt ? "methods" : nullptr);  // 附加方法列表
                lists->methods.clear();  // 清空方法列表
            }
            lists->methods.add(mlist);  // 添加方法列表到列表中
            fromBundle |= entry.hi->isBundle();  // 更新是否来自 bundle 标记
        }

        property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi);  // 获取分类的属性列表
        if (proplist) {
            if (lists->properties.isFull()) {
                rwe->properties.attachLists(lists->properties.array, lists->properties.count, isPreattached, PrintPreopt ? "properties" : nullptr);  // 附加属性列表
                lists->properties.clear();  // 清空属性列表
            }
            lists->properties.add(proplist);  // 添加属性列表到列表中
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);  // 获取分类的协议列表
        if (protolist) {
            if (lists->protocols.isFull()) {
                rwe->protocols.attachLists(lists->protocols.array, lists->protocols.count, isPreattached, PrintPreopt ? "protocols" : nullptr);  // 附加协议列表
                lists->protocols.clear();  // 清空协议列表
            }
            lists->protocols.add(protolist);  // 添加协议列表到列表中
        }
    }

    // 附加分类的辅助函数
    auto attach = [&](Lists *lists, bool isPreattached) {
        if (lists->methods.count > 0) {
            prepareMethodLists(cls, lists->methods.begin(), lists->methods.count,
                               NO, fromBundle, __func__);  // 准备方法列表
            rwe->methods.attachLists(lists->methods.begin(), lists->methods.count, isPreattached, PrintPreopt ? "methods" : nullptr);  // 附加方法列表
            if (flags & ATTACH_EXISTING) {
                flushCaches(cls, __func__, [](Class c){
                    // 常量缓存已经在 prepareMethodLists 中处理了
                    // 如果类仍然是常量的,这里保留
                    return !c->cache.isConstantOptimizedCache();
                });
            }
        }

        rwe->properties.attachLists(lists->properties.begin(), lists->properties.count, isPreattached, PrintPreopt ? "properties" : nullptr);  // 附加属性列表

        rwe->protocols.attachLists(lists->protocols.begin(), lists->protocols.count, isPreattached, PrintPreopt ? "protocols" : nullptr);  // 附加协议列表
    };

    // 附加预附加分类和正常分类
    attach(&preattachedLists, true);
    attach(&normalLists, false);
}

这段代码首先定义用于存储分类中方法、属性和协议的缓冲区。
然后遍历传入的分类列表,对于每个分类,提取方法、属性和协议列表。根据是否为预附加分类选择合适的缓冲区 (preattachedLists 或 normalLists)。如果缓冲区满了,则调用 prepareMethodLists 函数整理缓冲区中的方法列表(比如排序、去重),然后调用rwe->methods.attachLists方法直接将整理好的方法列表附加到类的扩展数据rwe中。然后再清空缓冲区,准备放入后面的分类数据。

分类的懒加载类和非懒加载类

  • 主类是非懒加载,其分类也会标记为非懒加载,提前进行加载
  • 如果分类是非懒加载,会促使主类也变为非懒加载
  • 如果主类和分类都是懒加载,则只会在第一次调用方法时加载
    注:这里说的类的加载指构造rwe。

在这里插入图片描述

分类的使用场合

  • ① 给一个类添加新的方法,可以为系统的类扩展功能。
  • ② 分解体积庞大的类文件,可以将一个类按功能拆解成多个模块,方便代码管理。
  • ③ 创建对私有方法的前向引用:声明私有方法,把 Framework 的私有方法公开等。直接调用其他类的私有方法时编译器会报错的,这时候可以创建一个该类的分类,在分类中声明这些私有方法(不必提供方法实现),接着导入这个分类的头文件就可以正常调用这些私有方法。
  • ④ 向对象添加非正式协议:创建一个 NSObject 或其子类的分类称为 “创建一个非正式协议”。 (正式协议是通过 protocol 指定的一系列方法的声明,然后由遵守该协议的类自己去实现这些方法。而非正式协议是通过给 NSObject 或其子类添加一个分类来实现。非正式协议已经渐渐被正式协议取代,正式协议最大的优点就是可以使用泛型约束,而非正式协议不可以。)

分类总结

  • 对于运行时加载的分类情况:1.类与分类都实现load方法;2.分类有两个以上实现load方法
  • 对于编译时加载的分类情况:1.类实现load方法,分类没有;2.类没有实现,分类仅有一个实现;3.类与分类均没实现
    所以希望不要动不动就实现load方法,很耗时,除了一些必要的操作。我们可以去执行initialize方法。

Q&A

Q:分类的对象方法,类方法都存在哪里?

一个类的所有分类的对象方法放在类对象中,所有分类的类方法存放在元类中

Q:分类的方法是如何添加到类对象方法列表中的?

大概流程

  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中
  3. 后面参与编译的Category数据,会在数组的前面
  4. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

Q:Category的实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Q:Category和Class Extension的区别是什么?

Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

Q:+load方法调用顺序?

  1. 先调用类的+load方法

1.1按照编译先后顺序调用(先编译,先调用)
1.2先调用父类的+load再调用子类的+load

2.** 再调用分类的+load方法**

2.1按照编译先后顺序调用(先编译,先调用)

// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
    call_class_loads();
}

// 2. Call category +loads ONCE
more_categories = call_category_loads();
  • 每个类、分类的+load,在程序运行过程中只调用一次,只有在加载类时候调用一次
  • 不存在分类的+load方法覆盖类的+load方法

Q:load、initialize的调用顺序?

  1. load
    1) 先调用类的load
    a) 先编译的类,优先调用load
    b) 调用子类的load之前,会先调用父类的load
    2) 再调用分类的load
    a) 先编译的分类,优先调用load

  2. initialize
    1> 先初始化父类
    2> 再初始化子类(可能最终调用的是父类的initialize方法)

关联对象实现原理

实现关联对象技术的核心对象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

objc_setAssociatedObject方法

这个方法是用于动态添加关联对象的方法,其实现:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

在其内部调用了_object_set_associative_reference方法:

_object_set_associative_reference

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // 这段代码用于处理当 object 和 key 为 nil 时的情况。一些代码可能依赖于此不会崩溃。
    // 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 类型。
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 根据策略和值创建关联对象。
    //四个核心对象之一ObjcAssociation
    ObjcAssociation association{policy, value};

    // 在锁外保留新值(如果有)。
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        // 管理关联对象的管理器。
        //四个核心对象之一AssociationsManager
        AssociationsManager manager;
        // 获取关联对象的哈希表。
        //四个核心对象之一AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {
            // 尝试在哈希表中插入关联对象,如果是首次插入,则返回 true。
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//四个核心对象之一AssociationsHashMap
            if (refs_result.second) {
                /* 这是我们第一次建立关联 */
                isFirstAssociation = true;
            }

            /* 建立或替换关联对象 */
            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;
                // 查找指定 key 的关联对象。
                auto it = refs.find(key);
                if (it != refs.end()) {
                    // 交换并移除旧的关联对象。
                    association.swap(it->second);
                    refs.erase(it);
                    // 如果没有关联对象了,则移除对象的关联对象。
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    // 在锁外调用 setHasAssociatedObjects,因为这将调用对象的 _noteAssociatedObjects 方法,
    // 这可能会触发 +initialize 方法,可能会做任意操作,包括设置更多的关联对象。
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // 释放旧值(在锁外)。
    association.releaseHeldValue();
}

下面来逐步分析四个核心对象:

AssociationsManager和AssociationsHashMap

class AssociationsManager {
   //声明一个映射值为ObjectAssociationMap的_mapStorage成员变量
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    // 构造函数,在对象创建时调用,锁定全局锁
    AssociationsManager()   { AssociationsManagerLock.lock(); }
   // 析构函数,在对象销毁时调用,解锁全局锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    // 返回存储对象的引用
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
    // 初始化存储对象的静态方法
    static void init() {
        _mapStorage.init();
    }
};

AssociationsHashMap

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

可以看出,在AssociationsManager内部有一个哈希表AssociationsHashMap,在这个哈希表中,键是objc_object(即我们传入的object对象),值是ObjectAssociationMap

ObjcAssociation

class ObjcAssociation {
    uintptr_t _policy;//策略
    id _value; //value值
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}
    ObjcAssociation(const ObjcAssociation &other) = default;
    ObjcAssociation &operator=(const ObjcAssociation &other) = default;
    ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
        swap(other);
    }

    inline void swap(ObjcAssociation &other) {
        std::swap(_policy, other._policy);
        std::swap(_value, other._value);
    }

    inline uintptr_t policy() const { return _policy; }
    inline id value() const { return _value; }

    inline void acquireValue() {
        if (_value) {
            switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
            }
        }
    }

    inline void releaseHeldValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            objc_release(_value);
        }
    }

    inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value);
        }
    }

    inline id autoreleaseReturnedValue() {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
            return objc_autorelease(_value);
        }
        return _value;
    }
};

ObjcAssociation中,保存了我们传入的策略_policy和value值_value,并提供了方法来保留、释放、复制和自动释放这些对象。通过使用这个类,可以安全地管理关联对象的生命周期,并确保在不同操作下的一致性。

_object_set_associative_reference的流程

_object_set_associative_reference的代码中还调用了一个acquireValue函数,这个函数是用于通过对策略的判断返回不同的值:

static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

_object_set_associative_reference的代码中:
首先根据我们传入的value经过acquireValue函数处理返回了new_value。
之后创建AssociationsManager manager,得到manager内部的AssociationsHashMapassociations
之后我们看到了我们传入的第一个参数object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object
如果传入的value为空,那么就删除这个关联对象;如果传入的value不为空,那么就尝试在哈希表中插入关联对象,如果是首次插入,则返回 true。
在哈希表插入关联对象的流程:

try_emplace

在哈希表插入关联对象用到了auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});这句代码,我们来看看try_emplace

template <typename... Ts>
  std::pair<iterator, bool> try_emplace(KeyT &&Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    TheBucket =
        InsertIntoBucket(TheBucket, std::move(Key), std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false;
    }

首先创建一个空的桶 TheBucket。桶用于在哈希表中查找或存储键值对。
调用 LookupBucketFor,尝试在哈希表中查找与传入的 Key对应的桶。如果找到了桶(即键已经存在),则返回指向该桶的迭代器和 false,表示未添加新的元素。
如果哈希表中没有找到对应的桶,则调用 InsertIntoBucket将新创建的空桶 TheBucket 存入 AssociationsHashMap 中。(此时还没有存入policyvalue,它们在assocition对象中)
然后调用refs.try_emplaceassociation(包含 policy 和 value)与 Key 关联起来,存入 refs。Key存bucket的first,value存second。

  • 关联对象并不存储在被关联对象本身内存中,而是有一个全局统一的 AssociationsManager中
  • 一个实例对象就对应一个ObjectAssociationMap,
  • 而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,
  • ObjcAssociation中存储着关联对象的value和policy策略

objc_getAssociatedObject

objc_getAssociatedObject用于取出关联对象对应的值_value。

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 对象
    ObjcAssociation association{};

    {
        // 创建 AssociationsManager 对象,管理关联对象
        AssociationsManager manager;
        // 获取 AssociationsHashMap 的引用
        AssociationsHashMap &associations(manager.get());

        //在associations哈希表中查找给定对象object的关联对象
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            // 找到对象的关联对象后,获取 ObjectAssociationMap
            ObjectAssociationMap &refs = i->second;
            // 在ObjectAssociationMap中查找指定键的关联对象
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                // 找到关联对象后,保存其值到 association
                association = j->second;
                // 保留关联对象的返回值
                association.retainReturnedValue();
            }
        }
    }

    // 返回与键关联的对象,并自动释放
    return association.autoreleaseReturnedValue();
}

retainReturnedValueObjcAssociation中的一个内联函数:

inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
          //增加 _value 对象的引用计数
            objc_retain(_value);
        }
    }

autoreleaseReturnedValue也是ObjcAssociation中的一个内联函数:

inline id autoreleaseReturnedValue() {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
          //标记_value自动释放
            return objc_autorelease(_value);
        }
        //返回_value
        return _value;
    }

这段代码中传入的值为我们要访问的关联对象object和用于在AssociationsHashMap中获取对应value的key
首先获取AssociationsManager中的AssociationsHashMap,然后在AssociationsHashMap中查找给定对象object的关联对象,找到后获取其对应的ObjectAssociationMap,然后在ObjectAssociationMap中查找传入的key的关联对象,找到后将其值保存到association并调用retainReturnedValue增加_value的引用计数。最后返回与键关联的对象的_value,并自动释放。

objc_removeAssociatedObjects

objc_removeAssociatedObjects函数用来删除所有关联对象,内部调用了_object_remove_assocations:

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_associations(object, /*deallocating*/false);
    }
}
void _object_remove_associations(id object, bool deallocating)
{
    // 创建一个 ObjectAssociationMap 对象,用于存储关联对象
    ObjectAssociationMap refs{};

    {
        // 创建 AssociationsManager 对象,管理关联对象
        AssociationsManager manager;
        // 获取 AssociationsHashMap 的引用
        AssociationsHashMap &associations(manager.get());

        // 在 associations 哈希表中查找给定对象 object 的关联对象
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            // 找到对象的关联对象后,将其内容交换到 refs 中
            refs.swap(i->second);

            // 如果不是在销毁过程中,SYSTEM_OBJECT 关联对象会被保留
            bool didReInsert = false;
            if (!deallocating) {
                // 遍历所有关联对象
                for (auto &ref: refs) {
                    // 检查是否是 SYSTEM_OBJECT 类型的关联对象
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        // 重新插入到哈希表中
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            // 如果没有重新插入任何对象,则从哈希表中删除该对象的关联
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // 在正常释放后需要释放的关联对象
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // 释放所有的关联对象(在锁外进行)
    for (auto &i: refs) {
        // 检查是否是 SYSTEM_OBJECT 类型的关联对象
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // 如果不是在销毁过程中,RELEASE_LATER 类型的关联对象不会被释放
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            // 释放关联对象持有的值
            i.second.releaseHeldValue();
        }
    }
    // 对于需要稍后释放的对象,进行释放操作
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

在这段代码中:
首先创建一个 ObjectAssociationMap 类型的局部变量 refs。这个变量用于暂时保存和处理与对象关联的所有关联对象。然后获取AssociationsManager中的AssociationsHashMap,然后在AssociationsHashMap中查找给定对象object的关联对象,找到后获取其对应的ObjectAssociationMap,然后将其内容交换到 refs 中,然后在refs中遍历所有的关联对象,检查遍历到的对象有没有SYSTEM_OBJECT策略,如果有,将其重新插入到 associations 哈希表中;如果没有,就从哈希表中删除该对象的关联。然后在锁外释放所有的关联对象。如果不是在销毁过程中,RELEASE_LATER 类型的关联对象不会被释放。

总的来说

关联对象流程

设值:

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

取值:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/872426.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何选择合适的变压吸附制氧设备

在选择合适的变压吸附(Pressure Swing Adsorption, PSA)制氧设备时&#xff0c;需要综合考虑多个因素以确保设备能够高效、稳定地运行&#xff0c;满足特定应用场景的需求。以下是一些关键步骤和考虑因素&#xff0c;帮助您做出明智的决策。 1. 明确应用需求 明确您的制氧需求至…

visual studio 2022更新以后,之前的有些工程编译出错,升级到Visual studio Enterprise 2022 Preview解决

系列文章目录 文章目录 系列文章目录前言一、解决方法 前言 今天遇到一个问题&#xff1a;visual studio 2022升级成预览版以后&#xff0c;之前的有些工程编译出错。首先代码、项目设置都没有改变&#xff0c;只是更新了visual studio 2022。 在编译工程时&#xff0c;编译器…

Team Render 上的 Redshift 照明与我的编辑机器上的不同(如何缓存 Redshift GI)

有时&#xff0c;您的灯光在另一台机器&#xff08;例如属于 Team Render 农场的机器&#xff09;上看起来会与在主/编辑机器上看起来不同。这是因为&#xff0c;即使使用相似或相同的硬件&#xff0c;一台机器计算全局照明的方式与另一台机器也会有所不同。 这可能会导致光线…

Docker 部署 Kibana (图文并茂超详细)

部署 Kibana ( Docker ) [Step 1] : 拉取 Kibana 镜像 docker pull kibana:7.14.0[Step 2] : 创建目录 ➡️ 启动容器 ➡️ 拷贝文件 ➡️ 授权文件 ➡️ 删除容器 # 创建目录 mkdir -p /data/kibana/{conf,plugins}# 启动容器 docker run --name kibana --restartalways \…

科普神文,一次性讲透AI大模型的核心概念

令牌&#xff0c;向量&#xff0c;嵌入&#xff0c;注意力&#xff0c;这些AI大模型名词是否一直让你感觉熟悉又陌生&#xff0c;如果答案肯定的话&#xff0c;那么朋友&#xff0c;今天这篇科普神文不容错过。我将结合大量示例及可视化的图形手段&#xff0c;为你由浅入深一次…

Centos Stream9系统安装及网络配置详解

1.镜像下载 如未拥有系统镜像文件的伙伴可通过前往下面的连接进行下载&#xff0c;下载完成后需将其刻录至U盘中。 PS&#xff1a;该U盘应为空盘&#xff0c;刻录文件会导该盘格式化&#xff0c;下载文件选择dvd1.iso完整包&#xff0c;适用于本地安装。 下载地址&#xff1…

恋爱相亲交友系统源码原生源码可二次开发APP 小程序 H5,web全适配

直播互动&#xff1a;平台设有专门的直播间&#xff0c;允许房间主人与其他异性用户通过视频连线的方式进行一对一互动。语音视频交流&#xff1a;异性用户可以发起语音或视频通话&#xff0c;以增进了解和交流。群组聊天&#xff1a;用户能够创建群聊&#xff0c;邀请自己关注…

【云计算】什么是云计算服务|为什么出现了云计算|云计算的服务模式

文章目录 什么是云计算服务本地部署VS云计算SaaS PaaS IaaS公有云、私有云、混合云为什么优先发展云计算服务的厂商是亚马逊、阿里巴巴等公司 什么是云计算服务 根据不同的目标用户&#xff0c;云计算服务&#xff08;Cloud Computing Services&#xff09;分为两种&#xff1…

探索动销方案创新路径,开启企业增长新引擎

在当今竞争激烈的市场中&#xff0c;动销方案的重要性不言而喻。然而&#xff0c;传统动销手段已难以应对多变的市场环境&#xff0c;企业急需探索创新路径。 当前动销方案面临哪些挑战呢&#xff1f; 首先&#xff0c;消费者需求越发多样化&#xff0c;他们追求个性化和多元化…

如何修复软件中的BUG

笔者上一篇博文《如何开发出一款优秀的软件》主要讲了如何开发一款优秀的软件及相应的必要条件。但对一个已上线&#xff0c;已经成型的产品&#xff0c;该如何解决存在的bug呢&#xff1f;这是本文要阐述的内容。 在这里&#xff0c;首先说一下bug的种类及bug严重程度分类&…

QT: Unable to create a debugging engine.

1.问题场景&#xff1a; 第一次安装QT&#xff0c;没有配置debug功能 打开控制面板》程序》找到Kit 重启电脑即可 2.问题场景&#xff1a; qt原本一直好好的&#xff0c;突然有天打开运行调试版本&#xff0c;提示Unable to create a debugging engine.错误。这个是指无法创…

【计算机网络】TCP连接如何确保传输的可靠性

一、确保可靠传输的机制 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、提供可靠交付的、面向字节流的、支持全双工的传输层通信协议 1、序列号 seq TCP头部中的序号&#xff0c;占32位&#xff08;4字节&#xff09;&#xff1b; 发送方给报文段分配一个序列号&a…

如何锻炼自己深度思考的能力?4个方法让你快速看清事物的本质!

我们每天都会接触到海量的信息&#xff0c;但真正的智慧并不在于掌握多少信息&#xff0c;而在于如何从中提炼出有价值的知识&#xff0c;并对其进行深刻的理解与运用。 本周想和大家探讨一下深度思考的重要性&#xff0c;同时分享一些实用的方法和技巧&#xff0c;希望能帮你…

STM32(一)简介

一、stm32简介 1.外设接口 通过程序配置外设来完成功能 2.系统结构 3.引脚定义 4.启动配置 5.最小系统电路

【数据结构初阶】二叉树--堆(顺序结构实现)

hello&#xff01; 目录 一、实现顺序结构二叉树 1.1 堆的概念和结构 1.2 堆及二叉树的性质 1.3 堆的实现 1.3.1 创建堆的结构 1.3.2 初始化和销毁 1.3.3 入堆向上调整算法&#xff08;创建一个小堆&#xff09; 1.3.4 出堆向下调整算法&#xff08;小堆&#x…

2024Java基础总结+【Java数据结构】(2)

面向对象07&#xff1a;简单小结类与对象 面向对象08&#xff1a;封装详解 面向对象09&#xff1a;什么是继承 ctrlh看类的关系&#xff0c;所有的类都默认的或间接继承Object 面向对象10&#xff1a;Super详解 super注意点: super调用父类的构造方法&#xff0c;必须在构造方…

OCR经典神经网络(一)文本识别算法CRNN算法原理及其在icdar15数据集上的应用

OCR经典神经网络(一)文本识别算法CRNN算法原理及其在icdar15数据集上的应用 文本识别是OCR&#xff08;Optical Character Recognition&#xff09;的一个子任务&#xff0c;其任务为&#xff1a;识别一个固定区域的的文本内容。 在OCR的两阶段方法里&#xff0c;文本识别模型接…

七,Spring Boot 当中的 yaml 语法使用

七&#xff0c;Spring Boot 当中的 yaml 语法使用 文章目录 七&#xff0c;Spring Boot 当中的 yaml 语法使用1. yaml 的介绍2. yaml 基本语法3. yaml 数据类型4. 学习测试的准备工作4.1 yaml 字面量4.2 yaml 数组4.3 yaml 对象 5. yaml 使用细节和注意事项6. 总结&#xff1a;…

2024高教社杯数学建模竞赛解题思路

高教社杯数学建模竞赛解题思路&#xff1a;独家出版&#xff0c;思路解析模型代码结果可视化。 A题思路及程序链接&#xff1a;https://mbd.pub/o/bread/ZpqblJZs B题思路及程序链接&#xff1a;https://mbd.pub/o/bread/ZpqblJZx D题思路及程序链接&#xff1a;https://mbd.pu…

常用排序算法(上)

目录 前言&#xff1a; 1.排序的概念及其运用 1.1排序的概念 1.2排序运用 1.3 常见的排序算法 2.常见排序算法的实现 2.1 堆排序 2.1 1 向下调整算法 2.1 2 建堆 2.1 3 排序 2.2 插入排序 2.1.1基本思想&#xff1a; 2.1.2直接插入排序&#xff1a; 2.1.3 插…