runtime
runtime 是iOS的運行時,用于實現iOS加載和調用屬性和方法。
函數中l(wèi)oad方法沒有使用runtime機制,是底層直接調用的函數。load執(zhí)行順序是由編譯時的文件順序相同,先編譯的先執(zhí)行l(wèi)oad,類優(yōu)先于分類的順序調用 +load 方法。
initialize
+initialize 方法是在類或類的子類收到第一條消息之前被調用的,這里所指的消息包括實例方法和類方法的調用。
也就是說 +initialize 方法是以懶加載的方式被調用的,如果一直沒有給一個類或他的子類發(fā)送消息,那么這個類的 +initialize 方法是永遠不會調用的。
當我們向某個類發(fā)送消息時,runtime 會調用 IMP lookUpImpOrForward(...) 這個函數在類中查找相應方法的實現或進行消息轉發(fā),打開 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());
}
// 發(fā)送調用類方法initialize的消息
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
......
}
在這里,先是對入參的父類進行遞歸調用,以確保父類優(yōu)先于子類初始化。
+initialize 方法在 runtime 中是以發(fā)送消息的方式調用的,所以子類會覆蓋父類的實現,分類會覆蓋類的實現,多個分類只會調用一個分類的 +initialize 方法。
分類
通過runtime動態(tài)將分類的屬性和方法合并到類對象,元類對象中。
- 擴展屬性
#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
方法交換之后,“方法的實現” 變成了 “你的處理代碼” + “方法的實現”
//獲取通過SEL獲取一個方法
class_getInstanceMethod
//獲取一個方法的實現
method_getImplementation
//獲取一個OC實現的編碼類型
method_getTypeEncoding
//給方法添加實現
class_addMethod
//用一個方法的實現替換另一個方法的實現
class_replaceMethod
//交換兩個方法的實現
method_exchangeImplementations
+ (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 掩碼,一般用來進行按位(與&)運算。