`map_images`和`load_images`(下)

740 阅读6分钟

上篇文章 中,我们探索了load_images的作用和map_images的部分原理,我们知道了类的加载实际是通过realizeClassWithoutSwift进行的加载,接下来我们继续进行探索。

realizeClassWithoutSwift最下面是进行处理分类methodizeClass,我们继续查看其实现

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();
    
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        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);
    ......
}

我们看到,其主要就是将ro的属性、方法、协议赋值给rwe的操作。我们运行进去后其实rwe是nil,那rwe在哪里进行的初始化呢?我们之前也说过,有分类的时候可能会创建rwe,我们先在源码中全局搜索rwe =来查看其创建位置,我们可以查到类似rwe = cls->data()->extAllocIfNeeded()的的方法。所以我们可以确定rwe是由extAllocIfNeeded进行的创建,我们搜索其创建位置:

  • 运行时方法:_class_addPropertyclass_addProtocolclass_setVersionaddMethodaddMethods调用的addMethods_finish
  • 其他方法:attachCategoriesobjc_class::demangledNameobjc_duplicateClass。 在源码中对这些方法进行断点WTPersonif (strcmp(cls->nonlazyMangledName(), "WTPerson") == 0 && !isMeta) { },我们发现创建了分类可是并没有走任何的断点,也就是说并没有创建rwe20220516105036907.png 那添加分类后什么时候会创建rwe呢?我们在分类里添加+load方法,然后发现只有同时实现类的+load和分类的+load方法后才会在attachCategories中命中调试。所以我们可以得出结论
  • 创建的分类和本类都是非懒加载类时才会初始化rwe。 我也也从断点的堆栈信息中可以得到attachCategoriesload_images -> loadAllCategories -> load_categories_nolock中进行的调用。

分类初始化

首先我们来查看一下分类中都可以存储哪些数据

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

我们可以看到其内部包含实例方法类方法,类的属性及协议、元类的属性及协议,所以我们可以得出分类是没有元类的,分类的数据结构中没有ivars,所以分类无法存储成员变量

我们将类和分类都写在main.m文件中,然后用clang编译一下main.m文件,我们发现我们可以获取到属性和我们自己写的方法,可是属性的settergetter方法我们无法在cpp文件中查询到,也就是说分类中没有实现属性的settergetter方法,需要我们在分类中自己去写。

我们在类中写settergetter方法是直接对类的成员变量进行的操作,分类中没有成员变量,所以我们需要用到runtime的关联对象进行赋值及取值操作 20220516121514958.png

objc_setAssociatedObject和objc_getAssociatedObject

在源码中我们可以看到objc_setAssociatedObject就是调用的_object_set_associative_reference函数,我们查看一下其实现

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) {
    ......
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();
    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        if (value) {
            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};,其将我们需要关联的对象object(self)封装成DisguisedPtr的类型,统一格式方便后面使用。

第二个操作ObjcAssociation association{policy, value};, 基于policy将value进行封装,ObjcAssociation的方法acquireValuereleaseHeldValue会根据policy策略对value进行retainrelease操作。

  • OBJC_ASSOCIATION_ASSIGN assgin
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC    strong nonatomic  
  • OBJC_ASSOCIATION_COPY_NONATOMIC copy nonatomic            
  • OBJC_ASSOCIATION_RETAIN strong atomic
  • OBJC_ASSOCIATION_COPY copy atomic
AssociationsManager

看到这个我们的第一想法就是单例,因为manager标识我们最常用的就是单例AssociationsManager其实并不是单例类,只是构造和析构函数,执行AssociationsManager manager;会调用构造函数,出了作用域会调用析构函数,也就是加锁及解锁的操作,为了保证同一时间只有一个线程操作AssociationsHashMap

    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
AssociationsHashMap

AssociationsHashMap是一个存储DisguisedPtrObjectAssociationMap的hash表,ObjectAssociationMap存储的就是ObjcAssociation

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

其获取方式是通过_mapStorage.get()获取的,_mapStorage是一个静态变量,说明AssociationsHashMap是在程序的整个内存存储中只有一份。

接下来会通过value是否有值进行判断,没有值的话会使用erase()删除hashbucket中的关联关系,有值会通过associations.try_emplace去获取迭代器(遍历整个集合的一种方法)

std::pair<iterator, bool> try_emplace(const 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, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true), true);
  }

熟悉的bucketT,也就是说先查询缓存中是否已经有一份object的关联对象缓存,没有找到会插入到缓存中,然后返回迭代器,然后根据这个迭代器进行数据更新。

以上是objc_setAssociatedObjectobjc_getAssociatedObject就是根据上面的迭代器根据key去进行查找

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();
}

最下面会将对象放入自动释放池中,所以我们不需要考虑内存管理问题。在对象dealloc的时候,会通过objc_rootDealloc -> object_dispose -> objc_destructInstance -> _object_remove_assocations,在_object_remove_assocations内部调用releaseHeldValue进行release操作。

attachLists

我们之前也了解了分类和本类都是非懒加载类时才会初始化rwe,同时在methodizeClassattachCategories中都是调用attachLists进行的数据插入,methodizeClassattachCategories先执行,我们来看一下插入的源码

    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[i];
            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;
            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[i] = addedLists[i];
            validate();
        }
    }

我们根据代码可以看到,其内部是存储的二维数组,每次都是将旧数据的方法列表放在后面,后面插入的方法数组会插入到最前面,分类在本类后调用,所以后编译的分类方法会放在最前面。

总结

rwt的初始化

  • 分类和本类都是非懒加载类时才会初始化
  • runtime动态添加属性、协议、方法时才会初始化 分类的属性需要使用runtime的关联对象处理setter及getter方法,其将关联对象放在全局唯一的AssociationsHashMaphash表中,根据key进行更新、读取和移除,同时将assocations放在自动释放池中,进行内存管理。

分类在本类后调用,所以后编译的分类方法会放在最前面。