iOS底层(十)-类拓展与load_Images

180 阅读7分钟

一、类拓展

1.1、什么是类拓展

类拓展实际上就是一个匿名的分类。 在.m文件中, @interface @end之间就是类的拓展。 为了给当前的类增加属性与方法。

1.2、类拓展的加载

我们知道在数据很早的时候都会来到 read_images 里面来对类进行处理。 通过镜像文件来读取类的各种信息。那么我们就可以在 read_images 中来看一下类中是否有类拓展中相关的东西, 这样就可以知道类拓展是否是在编译器就加载的。

首先来定义一个类以及拓展

类 Person.m:

#import "Person.h"
#import "Person+PExtension.h"

@interface Person ()
@property (nonatomic, copy) NSString *mName;
@end

@implementation Person
+ (void)load{
    NSLog(@"%s",__func__);
}

- (void)extM_method{
    NSLog(@"%s",__func__);
}
@end

类拓展 Person+PExtension.h :

@interface Person ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_subject;
- (void)extH_method;
@end

接下来我们在read_images中等待加载Person类的到来:

此时我们就可以通过lldb来看一下当前类中的ro中是否存在类拓展中的东西

可以看到这个时候ro中就已经有了拓展中相关的属性方法。

这也就是说类拓展会在编译时作为类的一部分来编译。

注: 一定要引用类拓展,系统才会去加载这个类拓展,否则不会对类拓展进行处理。

二、分类的属性关联

在上篇介绍到,当加载分类的时候, 会把分类的属性加到类的rw中去。但是此时我们去使用这个属性, 是没办法正常去使用的。原因就是它缺少了set get方法。这个时候我们就需要对它进行重新关联set get方法

@interface Person (Ca)
@property (nonatomic, copy) NSString *caName;

@end

@implementation Person (Ca)

-(void)setCaName:(NSString *)cate_name{
    /**
    参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
    参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
    参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
    参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
    */
    objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)caName{
    /**
    参数一:id object : 获取哪个对象里面的关联的属性。
    参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
    */
    return objc_getAssociatedObject(self, @"name");
}
@end

那么它是如何让属性与setget进行关联的呢

进入objc_setAssociatedObject() 往下走来到 _object_set_associative_reference()方法:

void _object_set_associative_reference(id object, 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;
    
    assert(object);
    
    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));
    
    // retain the new value (if any) outside the lock.
    // 在锁之外保留新值(如果有)。
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 关联对象的管理类
        AssociationsManager manager;
        // 获取关联的 HashMap -> 存储当前关联对象
        AssociationsHashMap &associations(manager.associations());
        // 对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数)
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            // 获取 AssociationsHashMap 的迭代器 - (对象的) 进行遍历
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                // 根据key去获取关联属性的迭代器
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    // 替换设置新值
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 到最后了 - 直接设置新值
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                // 如果AssociationsHashMap从没有对象的关联信息表,
                // 那么就创建一个map并通过传入的key把value存进去
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            // 如果传入的value是nil,并且之前使用相同的key存储过关联对象,
            // 那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    // 最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

看一下是如何处理关联的:

  1. 首先是创建了一个管理者, 用来管理各个类的哈希表。
  2. 管理者会通过地址来遍历找到相应的类。假如遍历不到类,则创建一张类的哈希表。
  3. 我们存储的属性名就是一个key通过下标法用这个key去在相应的类的哈希表中寻找这个相应的属性。假如找不到这个属性, 则创建一个属性通过key把值存进去。
  4. 如果传入一个nil值,则直接移除这个关联对象。

三、load_images

前篇章中说道过程序在_objc_init中通过_dyld_objc_notify_register()调用map_images。 那么这里就来继续向下,研究一下load_images.

我们通过实现load方法来将类分为懒加载类与非懒加载类。 但是无论是分类还是类,只要是实现load方法都会进行调用,那么load方法是在什么时候调用怎么调用的?

进入load_images() -> call_load_methods() 方法:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // 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);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}


void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 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();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

可以看到它进行了两个do-while循环, 分别来处理类的load方法 call_class_loads() 与分类的load方法call_category_loads()。也就是拿到所有的load方法后, 会有一个分支. 也就意味着前面会有处理用来区分类和分类的load.

唯一有额外处理的地方: prepare_load_methods((const headerType *)mh); 进入prepare_load_methods():

void prepare_load_methods(const headerType *mhdr)
{
   size_t count, i;

   runtimeLock.assertLocked();

   classref_t *classlist = 
       _getObjc2NonlazyClassList(mhdr, &count);
   for (i = 0; i < count; i++) {
       schedule_class_load(remapClass(classlist[i]));
   }

   category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
   for (i = 0; i < count; i++) {
       category_t *cat = categorylist[i];
       Class cls = remapClass(cat->cls);
       
       const class_ro_t *ro = (const class_ro_t *)cls->data();
       const char *cname = ro->name;
       const char *oname = "LGPerson";
       
       if (cname && (strcmp(cname, oname) == 0)) {
           printf("_getObjc2NonlazyClassList 类名 :%s  - %p 分类名: %s\n",cname,cls,cat->name);
       }
       
       if (!cls) continue;  // category for ignored weak-linked class
       if (cls->isSwiftStable()) {
           _objc_fatal("Swift class extensions and categories on Swift "
                       "classes are not allowed to have +load methods");
       }
       realizeClassWithoutSwift(cls);
       assert(cls->ISA()->isRealized());
       add_category_to_loadable_list(cat);
   }
}

我们知道只有实现load方法它才是一个懒加载类, 所以它会在非懒加载中遍历的到实现load方法的类列表. 进入schedule_class_load() -> add_class_to_loadable_list() 方法:

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

大致可以得知该方法进行了一个存储操作, 讲费懒加载类存储到了一个loadable_classes[]数组中, 当我们需要非懒加载类的时候, 直接到这个数组中去读取就可以了.

拿到了非懒加载类后, 系统就需要去调用load方法, 接着call_load_methods向下走. 来到对类与分类分支的地方.

现在明白了call_class_loads()应该就是调用load方法的地方, 进入 call_class_loads() :

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

对used进行循环, 在非懒加载数组loadable_class中遍历所有的非懒加载类, 拿到每一个非懒加载类的method,也就是load方法. 然后通过SEL直接将消息发送出去.

当类方法调用完成后, call_load_methods()方法中调用call_category_loads()来对分类的load进行加载. 此过程几乎与类的load方法调用一致.

并且根据代码可以得知, 是先处理主类的父类的load方法, 再调用主类的load方法. 再调用分类的load方法.

四、initalize的方法调用

我们知道initalize的方法调用时机也是非常早的, 但是在load_images调用完成了后我们仍看不到initialize的影子.

在Person中加入initalize的方法, 断点调试发现, 无论是Person类进行alloc还是直接class, initalize都会进行调用. 那么initalize方法就应该是跟方法调用的本质有关.

方法的本质就是发送消息, 那么又要来到我们熟悉的lookupimporforward()方法

其中有一段initialize相关的处理:

    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

进入initializeAndLeaveLocked()-> initializeAndMaybeRelock()->initializeNonMetaClass()->callInitialize中:

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

在里面直接通过objc_msgSend()调用了initialize()方法