runtime 是iOS的运行时,用于实现iOS加载和调用属性和方法。
函数中load方法没有使用runtime机制,是底层直接调用的函数。load执行顺序是由编译时的文件顺序相同,先编译的先执行load,类优先于分类的顺序调用 +load
方法。
initialize
+initialize
方法是在类或类的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。
也就是说 +initialize
方法是以懒加载的方式被调用的,如果一直没有给一个类或他的子类发送消息,那么这个类的 +initialize
方法是永远不会调用的。
当我们向某个类发送消息时,runtime
会调用 IMP lookUpImpOrForward(...)
这个函数在类中查找相应方法的实现或进行消息转发,打开 objc-runtime-new.h
找到这个函数:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
...
if (initialize && !cls->isInitialized()) {
// 类没有初始化时,对类进行初始化
_class_initialize (_class_getNonMetaClass(cls, inst));
// 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
}
...
}
从中可以看到当类没有初始化时,会调用 _class_initialize(Class cls)
对类进行初始化:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
BOOL reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
// 递归调用,对父类进行_class_initialize调用,确保父类的initialize方法比子类先调用
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
......
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);
// 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: calling +[%s initialize]",
cls->nameForLogging());
}
// 发送调用类方法initialize的消息
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
......
}
在这里,先是对入参的父类进行递归调用,以确保父类优先于子类初始化。
+initialize
方法在 runtime
中是以发送消息的方式调用的,所以子类会覆盖父类的实现,分类会覆盖类的实现,多个分类只会调用一个分类的 +initialize
方法。
分类
通过runtime动态将分类的属性和方法合并到类对象,元类对象中。
- 扩展属性
#import "ClassName + CategoryName.h"
#import <objc/runtime.h>
static void *strKey = &strKey;
@implementation ClassName (CategoryName)
-(void)setStr:(NSString *)str
{
objc_setAssociatedObject(self, &strKey, str, OBJC_ASSOCIATION_COPY);
}
-(NSString *)str
{
return objc_getAssociatedObject(self, &strKey);
}
Method Swizzling
每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。
交换前:Asel->AImp Bsel->BImp
交换后:Asel->BImp Bsel->AImp
方法交换之后,“方法的实现” 变成了 “你的处理代码” + “方法的实现”
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel {
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);
BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (didAddMethod) {
class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, swizMethod);
}
}
调用过程,涉及到了isa指针
isa
-
arm64之前,isa是普通指针
-
arm64 isa采用共用体数据结构,使用位域存储更多信息,将64位数据,分开来存储信息
-
isa共用体中的结构体没有实际意义,只是用来描述内存中每一部分的作用
-
isa只有33位用来存放类地址值(shiftcls在
4-36
位),需要&ISA_MASK才能像之前一样访问class、meta-class -
isa共用体的好处:
- 可以存放更多的信息
- 更好的利用内存空间
ps:MASK 掩码,一般用来进行按位(与&)运算。