iOS底层探索-分类的加载与关联对象

641 阅读6分钟

上篇 类的加载 中我们了解了 dyld 在编译阶段通过 _dyld_objc_notify_register 方法中的 map_images 对类进行加载的流程,这篇我们再来看一下如果在有分类时,分类加载的原理

1、Category(分类)

我们准备一段创建了分类的代码,为分类 尝试添加属性和方法 image.png

1.1、特性

  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加后无法读取(可通过Runtime给分类添加属性)
  • 分类中使用@property定义变量,只会生成变量的getter/setter方法的声明,不能生成方法的实现和下划线开头的成员变量

1.1、转译C++分析

  • 通过终端使用clang -rewrite-objc main.m将文件转译为.cpp文件
  • 打开cpp文件我们可以找到_category_t结构体,这里存放的就是 编译阶段(运行后会与本类放到一起) 产生的分类文件
    struct _category_t {
        const char *name;
        struct _class_t *cls;
        const struct _method_list_t *instance_methods;
        const struct _method_list_t *class_methods;
        const struct _protocol_list_t *protocols;
        const struct _prop_list_t *properties;
    };
    
    • 实例方法列表与类方法列表都放在这个 _category_t 结构中,不存在分元类这么个东西
    • _category_t 结构中 没有 ivars 的项,所以这也是为什么分类不能添加成员变量的原因

1.2、类扩展(匿名分类)

image.png

  • 可以给类添加成员变量、属性,但是为私有变量
  • 可以给类添加方法,也是私有方法

2、关联对象

1.1、为分类动态添加属性

虽然分类不能添加成员变量,但我们可以通过objc_setAssociatedObject(objcet, key, value, 关联策略)objc_getAssociatedObject(object, key)方法来给分类设置 属性 image.png

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

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
  • objc_setAssociatedObjectobjc_getAssociatedObject 为中间层代码,目的是对业务层进行隔离,因为底层API根据不同版本会发生变化,但中间层代码是保持不变的

1.2、objc_setAssociatedObject

_object_set_associative_reference
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
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //将关联策略与value封装为 ObjcAssociation
    ObjcAssociation association{policy, value};
    // retain the new value (if any) outside the lock.
    
    //对关联策略进行处理
    association.acquireValue();
    
    bool isFirstAssociation = false;
    {
        //加锁,保证同一时间只有一个线程操作 AssociationsHashMap
        AssociationsManager manager;
        //拿到全局唯一的存放Associations的哈希表
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //返回以 disguised(封装的object)为key的迭代器
            //若查到 disguised 则返回迭代器,若没有则以 disguised 为key,以 ObjectAssociationMap(封装的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();
}
  • AssociationsManager

    class AssociationsManager {
        using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
        //静态的哈希表,因此全局唯一
        static Storage _mapStorage;
    
    public:
        //C++的构造与析构函数,在构造时加锁,析构时开锁
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
        AssociationsHashMap &get() {
            //返回上边静态的哈希表,因此AssociationsHashMap也是全局唯一的
            return _mapStorage.get();
        }
    
        static void init() {
            _mapStorage.init();
        }
    };
    

    className() { 类或结构体构造时执行自定义方法 }
    ~className() { 类或结构体析构时执行自定义方法 }

    • 构造时加锁,析构时开锁,保证操作哈希表时线程安全
  • AssociationsHashMap

    //DenseMap就是张哈希表
    typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
    typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
    
    • AssociationsHashMap 中存的是:
      • DisguisedPtr对object的封装
      • ObjectAssociationMap将 封装了关联策略与值的ObjcAssociation,再封装成的哈希表
  • try_emplace

    // Inserts key,value pair into the map if the key isn't already in the map.
      // The value is constructed in-place if the key is not in the map, otherwise
      // it is not moved.
      template <typename... Ts>
      std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
        //类似cache中的 bucket_t,也是桶子容器
        BucketT *TheBucket;
        //LookupBucketFor 查找Key是否已存在
        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);
      }
    
    • disguised 为key查找返回迭代器,很好理解就是 先找设置了关联的类返回这个类中存放茫茫多关联属性的哈希表的迭代器

    • 若查到 disguised 则返回迭代器,没查到则先插入到表(以 disguised 为key、 ObjectAssociationMap(封装的value与关联策略)为值),再返回迭代器

1.3、objc_getAssociatedObject

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

    {
        //与 objc_setAssociatedObject 中类似的根据key找迭代器
        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();
}

小结

image.png

  • 唯一表 AssociationsHashMap 中有茫茫多 disguised 为key的哈希表

  • 每一个 disguised 是一个 设置了关联内容的类,里边有很多个 ObjectAssociationMap

  • 每一个 ObjectAssociationMap 中存放的是 自定义的key名ObjcAssociation(关联策略与value的封装)

3、Category加载

  • rwe:在 类通过runtime的api动态修改了方法、属性、协议 或者 类有分类并且都为非懒加载 的情况时才存在;里边以二维数组的形式存放数据
  • 思路:锁定 rwe 即可锁定分类的加载时机

类为非懒加载,分类为非懒加载:ro中只有类的数据,没有分类的数据;分类数据在rwe
类为懒加载,分类为非懒加载:类与分类中数据在ro中了,没有rwe
类为非懒加载,分类为懒加载:类与分类中数据在ro中了,没有rwe
类为懒加载,分类为懒加载:类与分类中数据在ro中了,没有rwe

3.1、从 rwe 入手探索分类加载

  • 之前我们探索类的时候按着 objc_class --> bits --> class_rw_t 路线曾经看过rwe中的结构 image.png
    • 在上边结构里我们可以根据方法名看到rwe的创建方法extAllocIfNeeded,那么接下来我们就可以寻找一下都在什么地方有调用这个方法

3.2、attachCategories (attach:附加)

image.png

  • attachCategories 方法中创建rwe

3.3、attachToClass / load_categories_nolock

  • 全局搜索,这两个方法会调用 attachCategories 去创建rwe
//attachToClass
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()) {
        category_list &list = it->second;
        if (flags & ATTACH_CLASS_AND_METACLASS) {
            int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
            // attachCategories
            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);
    }
}
//load_categories_nolock
static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[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.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                    //此处调用 attachCategories
                        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
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                       objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

3.4、attachLists

整合分类添加的方法到本类中 image.png

  • 上篇提到了使用realizeClassWithoutSwift中的methodizeClass 将分类方法添加到本类中也是通过这个方法实现的,也串联起来了 image.png