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)
复制代码