iOS 分类(Category)部分二

主要讲解 category 和+(void)load 的关系;

isa和 superclass 的指向关系, 请先看这篇文章, 不然下面很多知识可能会无法理解;
分类(Category)部分一
分类(Category)部分二
分类(Category)部分三
+(void)load和+(void)initialize 区别总结

1. 分类是否有+(void)load方法? 如果有, 分类的会不会覆盖类的+(void)load方法?

分类有+(void)load方法, 且不会覆盖类的+(void)load方法;

下面讲解下+(void)load的调用过程:

+(void)load方法会在runtime编译加载类和分类时调用, 通过函数指针的方式直接调用, 并不是通过objc_msgSend()的方式调用(此方式需要通过 isasuperclass来查找方法);

  1. +(void)load会在runtime加载类和分类时调用;
  2. 每个类/分类的+(void)load方法只会调用一次;
  3. 调用顺序多个类按照编译的顺序先后调用+(void)load方法;
  4. 调用子类的+(void)load方法时会先调用父类的+(void)load方法;
  5. 最后按照编译的顺序调用分类的+(void)load方法;

代码验证:

///Person继承 NSObject
#import 
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@end
NS_ASSUME_NONNULL_END

#import "Person.h"
@implementation Person
+ (void)load {
    NSLog(@"Person  +load 调用");
}
@end

#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Man : Person
@end
NS_ASSUME_NONNULL_END
///Man 继承 Person
#import "Man.h"
@implementation Man
 + (void)load {
     NSLog(@"Person 的子类 Man 的  +load 方法");
 }
@end

///NSObject的分类 AAA
#import "NSObject+AAA.h"
@implementation NSObject (AAA)
+ (void)load {
    NSLog(@"NSObject (AAA)   +load 调用");
}
@end

///NSObject的分类 BBB
#import "NSObject+BBB.h"
@implementation NSObject (BBB)
+ (void)load {
    NSLog(@"NSObject (BBB)   +load 调用");
}
@end
///Man 的分类 CCC
#import "Man+CCC.h"
@implementation Man (CCC)
+ (void)load {
    NSLog(@"Man (CCC)  +load 方法");
}
@end


2020-06-22 16:22:55.027773+0800 Category 分类补充[18306:244348] Person  +load 调用
2020-06-22 16:22:55.028488+0800 Category 分类补充[18306:244348] Person 的子类 Man 的  +load 方法
2020-06-22 16:22:55.028625+0800 Category 分类补充[18306:244348] Man (CCC)  +load 方法
2020-06-22 16:22:55.028720+0800 Category 分类补充[18306:244348] NSObject (BBB)   +load 调用
2020-06-22 16:22:55.028816+0800 Category 分类补充[18306:244348] NSObject (AAA)   +load 调用

objc4源码分析为什么类的+load 优先于分类的+load原因
///runtime 入口
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();
    ///load_images加载模块
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
===>
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    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);
        ///准备方法, 通过此方法可以确定为什么会先调用父类的+load 方法, 此流程只
        ///讲解为什么类的+load 方法会优先于分类的 category, 为什么父类优先想看下面的流程;
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    ///调用 +load 方法;
    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
        /*
        注意这个注释, 循环重复的调用+load 方法, 直到不再有+load 方法;
         将所有类的+load 方法调用完后才会调用 category 的+load 方法;
        */
        while (loadable_classes_used > 0) {
            ///调用 class 的+load方法
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        ///调用 category 的+load方法 一次
        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;
}

===>
///调用类的+load 方法
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;
        /*
          调用方法, classes[i]取出一个类, 然后调用其.method;  就是这个类的+load 方法实现;
      
        struct loadable_class {
          Class cls;  // may be nil
          ///类的+load 方法
          IMP method;
      };
        
        */
        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, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
===>
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        /*
          调用方法, cats[i]取出一个 分类, 然后调用其.method;  就是这个分类的+load 方法实现;

     struct loadable_category {
        Category cat;  // may be nil
        ///分类的+load 方法实现
        IMP method;
      };
    */
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;
...
}

objc4源码分析为什么父类类的+load会优先调用; 
///接着上个流程中准备方法的方法查看
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        ///循环遍历各个类, 然后规划类的 load 方法;
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        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, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
===>
static void schedule_class_load(Class cls)
{
    ///基类(NSObject )的superclass 为 nil, 则递归结束
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    /*
      看这句官方注释所说;   确保顺序是: 确保父类优先;  schedule_class_load递归调用,  
      一直传入 class 的superclass 指针, 直到查找到基类的superclass为止;
    */
    schedule_class_load(cls->superclass);
    ///把将class 加入到一个数组loadable_list中准备好;
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
===>

至此: 可以解释为什么会先调用父类的+(void)load 方法;


补充:
1. 如何更改分类的编译顺序?

如图所示, 哪个分类在前面则先编译;


2. 具体实例调用[Man load]打印出什么?

打印结果为Man (CCC) +load 方法;

#import "Man.h"

@implementation Man
+ (void)load {
     NSLog(@"Person 的子类 Man 的  +load 方法");
 }
@end

#import "Man+CCC.h"
@implementation Man (CCC)
+ (void)load {
    NSLog(@"Man (CCC)  +load 方法");
}
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    [Man load];
}

2020-06-22 16:24:40.694917+0800 Category 分类补充[18306:244348] Man (CCC)  +load 方法
     此处直接调用 load 方法, 最终转换为 objc_msgSend的方式;
     则查找load 方法顺序为, 首先查找元类中方法的列表. 由于分类的如果有方法跟类的方法同名, 
     则是优先调用分类的方法, 所以这个地方最终会调用分类的+load, 而不是 Man 的+load;
     #import "Man+CCC.h"
     @implementation Man (CCC)
     + (void)load {
         NSLog(@"Man (CCC)  +load 方法");
     }
     @end

参考: 调用方法的顺序


通过指令将OC文件转换为C++文件
指令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件.m -o 文件-arm64.cpp
如果需要链接其他框架, 使用-framework参数; 例: -framework UIKit


参考文章和下载链接
测试代码
Apple 一些源码的下载地址


有理解错误地方请不吝赐教

你可能感兴趣的:(iOS 分类(Category)部分二)