iOS 分类(Category)部分三

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

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

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

分类有+(void)initialize方法, 会"覆盖"类的+(void)initialize方法;

下面讲解下+(void)initialize的调用过程:
  1. +(void)initialize方法会在类第一次收到消息时调用;
  2. 先调用父类方法+(void)initialize, 然后再调用子类的+(void)initialize;
  3. 每个类/分类的+(void)initialize方法只会调用一次(特殊情况:父类的可能会调用多次);
  4. 如果多个分类实现+(void)initialize方法则后面编译的会覆盖前面的分类和类的+(void)initialize方法;

由于在objc4源码中没有找到直接的+(void)initialize实现流程,objc_msgSend的实现过程为汇编, 所以我们从侧面证明+(void)initialize的相关结论;
下面证明:

测试用代码; Person为NSObject的子类, Man和Woman是Person的子类, Man+CCC和Man+DDD为Man的分类;
///Person的实现
#import 
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@end
NS_ASSUME_NONNULL_END

#import "Person.h"
@implementation Person
+ (void)initialize
{
   NSLog(@"Person  +initialize 调用");
}
@end
///Woman的实现
#import "Woman.h"
@implementation Woman
+ (void)initialize
{
    NSLog(@"Woman  +initialize 调用");
}
@end
///Man的实现
#import "Man.h"
@implementation Man
+ (void)initialize
{
   NSLog(@"Man  +initialize 调用");
}
@end
///Man的分类CCC
#import "Man+CCC.h"
///如果分类实现, 则会覆盖原本类的 +initialize 方法;
+ (void)initialize
{
   NSLog(@"Man (CCC)  +initialize 调用");
}
@end
///Man的分类DDD
#import "Man+DDD.h"
@implementation Man (DDD)
///如果分类实现, 则会覆盖原本类的 +initialize 方法;如果多个分类, 则后面编译的调用;
+ (void)initialize
{
   NSLog(@"Man (DDD)  +initialize 调用");
}
@end

分类的顺序就是其编译顺序;
@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    ///将Man的两个分类中的+initialize 先屏蔽掉
    [Man alloc];
    [Man alloc];
    [Man alloc];
    [Man alloc];
    [Man description];
  ///其编译后的代码如下
}
@end

2020-06-22 20:43:00.983200+0800 Category 分类补充[1578:25250] Person  +initialize 调用
2020-06-22 20:43:00.983298+0800 Category 分类补充[1578:25250] Man  +initialize 调用

通过OC是通过消息发送机制; 可以知道OC代码编译后变为:

  objc_msgSend)((id)objc_getClass("Man"), sel_registerName("alloc"))
  objc_msgSend)((id)objc_getClass("Man"), sel_registerName("alloc"))
  objc_msgSend)((id)objc_getClass("Man"), sel_registerName("alloc"))
  objc_msgSend)((id)objc_getClass("Man"), sel_registerName("alloc"))
  objc_msgSend)((id)objc_getClass("Man"), sel_registerName("description"))

可以通过指令将文件转换为C++文件(即OC底层实现)在viewDidLoad中即可得到代码;

objc_msgSend发送消息, 对象Man收到消息后, 首先要做的就是通过class_getInstanceMethod查找方法, 然后调用;

/* method lookup */
enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_CACHE = 4,
    LOOKUP_NIL = 8,
};

///objc4源码分析
///查找方法开始
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
  /*由于类和元类的结构一样, 所以这里其实调用的是同一个方法, 如果入参是类对象则查找实例方法, 
  如果入参元类对象, 则查找类方法;   cls->getMeta()判断是否是元类;
    */
return class_getInstanceMethod(cls->getMeta(), sel);
}

==>

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
      /*
    Search method lists: 搜索方法列表;
    lookUpImpOrForward: 查找方法的实现;
      */
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

===>
///查找, behavior=LOOKUP_RESOLVER=2
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    //
    // TODO: this check is quite costly during process startup.
    checkIsKnownClass(cls);
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
      /*
      这里判断类是否有isInitialized()
      behavior=LOOKUP_RESOLVER=2       LOOKUP_INITIALIZE=1
      (behavior & LOOKUP_INITIALIZE = 1) &&
       (!(类如果没有isInitialized()=NO))
      = YES
      所以现在开始isInitialized();
     */
    if (slowpath((behavior & LOOKUP_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
    }

    runtimeLock.assertLocked();
    curClass = cls;
...
}

===>
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

===>
/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
注意注释: 发送`+initialize`给任何没有`initialize`的类
并且强制先`initialization`父类;
...
**********************************************************************/
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {
        if (!leaveLocked) lock.unlock();
        return cls;
    }

    // Find the non-meta class for cls, if it is not already one.
    // The +initialize message is sent to the non-meta class object.
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    // Realize the non-meta class if necessary.
    if (nonmeta->isRealized()) {
        // nonmeta is cls, which was already realized
        // OR nonmeta is distinct, but is already realized
        // - nothing else to do
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        // runtimeLock is now unlocked
        // fixme Swift can't relocate the class today,
        // but someday it will:
        cls = object_getClass(nonmeta);
    }

    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    ///开始initialize非元类的类
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}

===>
/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
注意注释: 发送`+initialize`给任何没有`initialize`的类
并且强制先`initialization`父类;
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    ///确保class在initialize时其父类已经完成initializing;
    // See note about deadlock above.
    /*
      开始递归调用initializeNonMetaClass()
      如果父类supercls不为nil, 并且supercls没有isInitialized();
      则一直往上查找,直至根类(NSObject)的superclass=nil; 然后开始执行;
      到这里可以印证为什么在类第一次接收到消息时为什么会调用initialize方法, 
      以及为什么会先调用父类的initialize方法;
    */
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;

            // Grab a copy of the will-initialize funcs with the lock held.
            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        for (auto callback : localWillInitializeFuncs)
            callback.f(callback.context, cls);

        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         objc_thread_self(), cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        //
        // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
        // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            ///调用 initialize 方法
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}
===>
void callInitialize(Class cls)
{
    /*
    最终到objc_msgSend, 开始通过 isa 和 superclass 查找方法initialize;
    第一次时总是优先调用父类的的 initialize 方法, 然后才会调用自己的initialize, 
    如果有分类实现了initialize则调用分类的不再调用自己的initialize方法;
    如果自己没有实现initialize方法也没有分类实现,而父类实现了initialize方法, 则第一次收到消息时
    通过 isa 和 superclass查找到父类的initialize方法, 然后调用;
    */
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

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


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


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

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