主要讲解 category 和+(void)initialize 的关系;
isa和 superclass 的指向关系, 请先看这篇文章, 不然下面很多知识可能会无法理解;
分类(Category)部分一
分类(Category)部分二
分类(Category)部分三
+(void)load和+(void)initialize 区别总结
1. 分类是否有+(initialize)
方法? 如果有, 分类的会不会覆盖类的+(void)initialize
方法?
分类有+(void)initialize
方法, 会"覆盖"类的+(void)initialize
方法;
下面讲解下+(void)initialize
的调用过程:
-
+(void)initialize
方法会在类第一次收到消息时调用; - 先调用父类方法
+(void)initialize
, 然后再调用子类的+(void)initialize
; - 每个类/分类的
+(void)initialize
方法只会调用一次(特殊情况:父类的可能会调用多次); - 如果多个分类实现
+(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 一些源码的下载地址
有理解错误地方请不吝赐教