super實際上是OC爲咱們提供的一個關鍵字,主要是繼承體系中用來調用類從父類繼承過來的屬性和方法,它只是一個標記,若是是使用super去調用方法,本質其實仍是拿到當前類對象,而後從其父類的緩存和方法列表進行查找。下面咱們就經過源碼來進一步探索super的底層實現。git
- (instancetype)init { self = [super init]; if (self) { NSLog(@"init"); } return self; } 複製代碼
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m
複製代碼
static instancetype _I_XLPerson_init(XLPerson * self, SEL _cmd) { self = objc_msgSendSuper((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("XLPerson")) }, sel_registerName("init")); if (self) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_XLPerson_d841fc_mi_0); } return self; } 複製代碼
能夠發現,在init方法中,[super init]最終轉換成了objc_msgSendSuper函數,函數具備兩個參數。github
而後到objc源碼中查找objc_msgSendSuper函數的實現,發如今源碼中存在objc_msgSendSuper和objc_msgSendSuper2兩個函數,那麼到底super最終執行的是哪一個函數呢?能夠經過Xcode斷點,查看彙編代碼來找到最終調用的方法。數組
首先咱們在XLPerson的init函數中打個斷點,而後在main函數中建立XLPerson對象,而且打開Xcode如下設置緩存
而後運行程序,查看彙編代碼,能夠明確看出,super最終調用的是objc_msgSendSuper2函數bash
而後在源碼中查看objc_msgSendSuper2函數聲明以下,objc_msgSendSuper2的前兩個參數分別對應上文中的結構體__rw_objc_super和sel_registerName("init")markdown
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
複製代碼
/// Specifies the superclass of an instance.
struct objc_super {
//class的實例對象,用來接收消息
__unsafe_unretained _Nonnull id receiver;
//父類對象,用來決定方法查找的起點
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
複製代碼
struct objc_super2 {
//當前消息接收者,實例對象
id receiver;
//當前類對象
Class current_class;
};
複製代碼
ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame //p0其實就是x0寄存器,p16其實就是x16寄存器 //p0中存放着真正的消息接收者,而p16存放着當前class對象 ldp p0, p16, [x0] // p0 = real receiver, p16 = class //拿到x16中保存的class對象地址,經過地址偏移拿到它的superclass的地址,從新賦值給p16寄存器 ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass //調用CacheLookup進行方法查找,而p16就是CacheLookup的參數 CacheLookup NORMAL END_ENTRY _objc_msgSendSuper2 複製代碼
在objc_msgSendSuper2中,第一個參數是一個objc_super2類型的結構體。在arm64彙編中,x0寄存器通常用來存放參數或者返回值,此處的x0就存放告終構體objc_super2的地址值。架構
ldp p0, p16, [x0]表示從x0寄存器裏存放的地址開始,取前8個字節的地址賦值給p0,取後8個字節的地址賦值給p16,此時,p0寄存器的值就是objc_super2結構體中receiver的地址,p16的值就是objc_super2結構體中current_class的地址。app
ldr p16, [x16, #SUPERCLASS]其實就是拿到current_class的superclass,[x16, #SUPERCLASS]其實就是將x16中存放的內存地址偏移8個字節。而Class底層結構中,第一個成員變量是isa指針,佔用8個字節,第二個成員就是superclass。而x16裏存放的地址值就是Class的地址值,偏移8個字節其實就是拿到了superclass指針。這也印證了上文中所說的,objc_msgSendSuper2函數的第一個參數實際上是objc_super2類型的結構體。iphone
SUPERCLASS其實就是當前架構下指針所佔字節數,在arm64架構中,指針類型佔8個字節。函數
拿到superclass指針以後,將superclass的地址值存放在p16寄存器中,而p16寄存器的值就是CacheLookup函數的參數,CacheLookup會到superclass的方法列表中去查找對應的方法。可是真正的消息接收者仍是當前的receiver。
通常狀況下,給對象receiver發送一個消息,首先會到receiver的緩存列表或者方法列表中去查找,找不到纔會到superclass中去查找,而super則表示首先會給receiver發送一個消息,可是會先到recever的superclass中進行方法查找。這就是爲何使用super會調用父類方法。
首先建立XLPerson類,而後建立XLTeacher類繼承自XLPerson類,而後在XLTeacher.m中增長以下代碼
@implementation XLTeacher - (instancetype)init { self = [super init]; if (self) { NSLog(@"%@", [self class]); NSLog(@"%@", [super class]); NSLog(@"%@", [self superclass]); NSLog(@"%@", [super superclass]); } return self; } @end 複製代碼
要想知道打印結果,還須要知道class和superclass的底層實現,在objc源碼的NSObject.mm中能夠看到具體的實現,以下
@implementation NSObject + (id)self { return (id)self; } - (id)self { return self; } + (Class)class { return self; } - (Class)class { return object_getClass(self); } + (Class)superclass { return self->superclass; } - (Class)superclass { return [self class]->superclass; } @end 複製代碼
首先,查看源碼中NSObject.mm的實現
@implementation NSObject + (BOOL)isMemberOfClass:(Class)cls { //經過object_getClass獲取當前類對象的isa所指向的元類對象與cls進行比較 return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { //比較當前實例對象的isa所指向的類對象與cls return [self class] == cls; } + (BOOL)isKindOfClass:(Class)cls { //遍歷當前類對象的元類對象以及它的父類的元類對象,若是和cls相等就返回YES for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { //遍歷當前實例對象所對應的類對象以及它的父類,若是和cls相等就返回YES for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } @end 複製代碼
建立XLPerson對象,繼承自NSObject。而後在main函數中增長如下代碼
int main(int argc, const char * argv[]) { @autoreleasepool { BOOL result1 = [NSObject isMemberOfClass:[NSObject class]]; BOOL result2 = [NSObject isKindOfClass:[NSObject class]]; BOOL result3 = [XLPerson isMemberOfClass:[XLPerson class]]; BOOL result4 = [XLPerson isKindOfClass:[XLPerson class]]; NSLog(@"%d %d %d %d", result1, result2, result3, result4); XLPerson *person = [[XLPerson alloc] init]; BOOL result5 = [person isMemberOfClass:[NSObject class]]; BOOL result6 = [person isKindOfClass:[NSObject class]]; BOOL result7 = [person isMemberOfClass:[XLPerson class]]; BOOL result8 = [person isKindOfClass:[XLPerson class]]; NSLog(@"%d %d %d %d", result5, result6, result7, result8); } return 0; } 複製代碼
利用runtime,咱們能夠遍歷類的全部屬性或者成員變量,拿到屬性或者成員變量咱們就能夠作不少事情,好比結合KVC給系統類對象的私有屬性賦值,或者將字典轉換成模型等等。
想給系統類的私有屬性賦值,前提是須要知道系統類有哪些私有屬性,首先,建立NSObject的分類NSObject+Ivar,以下
@interface NSObject (Ivar) + (void)logIvar; @end @implementation NSObject (Ivar) + (void)logIvar{ unsigned int count; Ivar *ivars = class_copyIvarList(self, &count); for (int i = 0; i < count; i++) { //取出成員變量 Ivar ivar = ivars[i]; NSString *ivarName = [[NSString alloc] initWithUTF8String:ivar_getName(ivar)]; NSLog(@"%@", ivarName); } free(ivars); } @end 複製代碼
而後以UITextField爲例,調用[UITextField logIvar]方法,就能夠打印出UITextField的全部成員變量,假如咱們須要修改輸入框提示語的文字顏色,就能夠找到其中的_placeholderLabel,而後經過KVC來拿到對應的屬性進行設置
UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 200, 30)]; field.placeholder = @"我是提示語"; [self.view addSubview:field]; [field setValue:[UIColor redColor] forKey:@"_placeholderLabel.textColor"]; 複製代碼
在iOS 13以後是不容許訪問系統類的私有成員變量的,此處僅僅是用做功能演示,運行會報錯。
OC開發過程當中,涉及到不少字典轉模型的需求,尤爲是和後臺進行交互。若是不使用runtime的話,就須要本身建立方法,本身去實現。以下
@interface XLPerson : NSObject @property(nonatomic, copy)NSString *name; @property(nonatomic, assign)int age; + (instancetype)personWithDic:(NSDictionary *)dic; @end @implementation XLPerson + (instancetype)personWithDic:(NSDictionary *)dic{ XLPerson *person = [[XLPerson alloc] init]; person.name = dic[@"name"]; person.age = [dic[@"age"] intValue]; return person; } @end 複製代碼
上述方法能夠實現字典轉模型的需求,可是若是存在字典套字典的這種狀況,那麼上述方法實現起來就會很是麻煩,所以,runtime的做用就體現了出來,典型的字典轉模型的工具如MJExtension也是利用runtime來實現。
建立NSObject的分類NSObject+Json,而後增長以下方法
@interface NSObject (Json) + (instancetype)objectWithDictionary:(NSDictionary *)dictionary; @end @implementation NSObject (Json) + (instancetype)objectWithDictionary:(NSDictionary *)dictionary{ id object = [[self alloc] init]; unsigned int count; Ivar *ivars = class_copyIvarList(self, &count); for (int i = 0; i < count; i++) { //取出成員變量 Ivar ivar = ivars[i]; NSMutableString *ivarName = [[NSMutableString alloc] initWithUTF8String:ivar_getName(ivar)]; //移除字符串以前的_ [ivarName deleteCharactersInRange:NSMakeRange(0, 1)]; //給對應的屬性設值 id value = dictionary[ivarName]; if (value) { [object setValue:value forKey:ivarName]; } } free(ivars); return object; } @end 複製代碼
使用方式以下
NSDictionary *dic = @{@"name" : @"張三", @"age" : @12}; XLPerson *person = [XLPerson objectWithDictionary:dic]; NSLog(@"%@ %d", person.name, person.age); 複製代碼
若是想要實現字典嵌套字典轉模型的方法,能夠參考MJExtension的實現。
以前的文章中說到過,類的全部對象方法都是存放在類對象的方法列表中,也就是存放在objc_class中的class_rw_t中,class_rw_t的源碼以下
struct class_rw_t {
uint32_t flags; //用來存放類的一些基本信息
uint32_t version; //版本號
const class_ro_t *ro; //class_ro_t類型指針
method_array_t methods; //方法列表
property_array_t properties;//屬性列表
protocol_array_t protocols; //協議列表
}
複製代碼
method_array_t是一個二維數組,內部存放了method_list_t,每一個method_list_t都存放着一個分類的全部方法,二維數組的最後一個method_list_t則存放着類編譯時就生成的全部方法,而method_list_t中存放的就是method_t,每個方法對應一個method_t,結構以下
struct method_t {
SEL name; //方法選擇器(方法名)
const char *types; //方法簽名
MethodListIMP imp; //方法實現的地址
}
複製代碼
在method_t中,imp則表示方法實現的地址,所以,若是想實現方法交換,只須要修改imp便可,而runtime就是經過交換兩個method_t的imp來實現方法交換的。
建立XLPerson類,以下
@interface XLPerson : NSObject - (void)run; - (void)sleep; @end @implementation XLPerson + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method methodRun = class_getInstanceMethod(self, @selector(run)); Method methodSleep = class_getInstanceMethod(self, @selector(sleep)); method_exchangeImplementations(methodRun, methodSleep); }); } - (void)run{ NSLog(@"%s", __func__); } - (void)sleep{ NSLog(@"%s", __func__); } @end 複製代碼
調用run方法和sleep方法會發現二者的實現被交換了,此處展現的是對象方法的交換,類方法交換和對象方法交換相似,更多方法交換實現能夠參考Aspect。
//動態建立一個類(參數:父類,類名,額外的內存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//註冊一個類(要在類註冊以前添加成員變量)
void objc_registerClassPair(Class cls)
//銷燬一個類
void objc_disposeClassPair(Class cls)
//獲取isa指向的Class
Class object_getClass(id obj)
//設置isa指向的Class
Class object_setClass(id obj, Class cls)
//判斷一個OC對象是否爲Class
BOOL object_isClass(id obj)
//判斷一個Class是否爲元類
BOOL class_isMetaClass(Class cls)
//獲取父類
Class class_getSuperclass(Class cls)
複製代碼
//獲取一個實例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
//拷貝實例變量列表(最後須要調用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//設置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
//動態添加成員變量(已經註冊的類是不能動態添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
//獲取成員變量的相關信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
複製代碼
//獲取一個屬性
objc_property_t class_getProperty(Class cls, const char *name)
//拷貝屬性列表(最後須要調用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//動態添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//動態替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
複製代碼
//得到一個實例方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
//方法實現相關操做
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
//拷貝方法列表(最後須要調用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//動態添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//動態替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//獲取方法的相關信息(帶有copy的須要調用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
//選擇器相關
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
//用block做爲方法實現
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
複製代碼