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")架構
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的地址值。app
ldp p0, p16, [x0]表示從x0寄存器裏存放的地址開始,取前8個字節的地址賦值給p0,取後8個字節的地址賦值給p16,此時,p0寄存器的值就是objc_super2結構體中receiver的地址,p16的值就是objc_super2結構體中current_class的地址。iphone
ldr p16, [x16, #SUPERCLASS]其實就是拿到current_class的superclass,[x16, #SUPERCLASS]其實就是將x16中存放的內存地址偏移8個字節。而Class底層結構中,第一個成員變量是isa指針,佔用8個字節,第二個成員就是superclass。而x16裏存放的地址值就是Class的地址值,偏移8個字節其實就是拿到了superclass指針。這也印證了上文中所說的,objc_msgSendSuper2函數的第一個參數實際上是objc_super2類型的結構體。函數
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)
複製代碼