super关键字
super其实是OC为我们提供的一个关键字,主要是继承体系中用来调用类从父类继承过来的属性和方法,它只是一个标记,如果是使用super去调用方法,本质其实还是拿到当前类对象,然后从其父类的缓存和方法列表进行查找。下面我们就通过源码来进一步探索super的底层实现。
源码解读
- 首先,先创建XLPerson类,并且在XLPerson.m中增加如下方法:
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"init");
}
return self;
}
- 然后通过以下指令,将XLPerson.m转成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m
- 查看生成的XLPerson.cpp文件,找到对应的init方法如下:
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函数,函数具有两个参数。
-
然后到objc源码中查找objc_msgSendSuper函数的实现,发现在源码中存在objc_msgSendSuper和objc_msgSendSuper2两个函数,那么到底super最终执行的是哪个函数呢?可以通过Xcode断点,查看汇编代码来找到最终调用的方法。
-
首先我们在XLPerson的init函数中打个断点,然后在main函数中创建XLPerson对象,并且打开Xcode以下设置
-
然后运行程序,查看汇编代码,可以明确看出,super最终调用的是objc_msgSendSuper2函数
-
-
然后在源码中查看objc_msgSendSuper2函数声明如下,objc_msgSendSuper2的前两个参数分别对应上文中的结构体__rw_objc_super和sel_registerName("init")
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
- 查看结构体objc_super的源码如下,其中objc_super有两个成员变量,一个是receiver,对应上文结构体中的self。一个是super_class,对应上文结构体中的class_getSuperclass(objc_getClass("XLPerson")。
/// 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 */
};
- 虽然objc_msgSendSuper2函数声明中第一个参数是objc_super类型的结构体,但是在实际传参过程当中,objc_msgSendSuper2函数第一个参数传递的其实是objc_super2类型的结构体,在下文的汇编代码中可以看出。
struct objc_super2 {
//当前消息接收者,实例对象
id receiver;
//当前类对象
Class current_class;
};
- 再次查找objc_msgSendSuper2函数的实现,发现objc_msgSendSuper2函数是由汇编来实现的,汇编代码如下
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的地址。
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。
super总结
- 通过super来调用方法时,底层会转换成objc_msgSendSuper2函数
- objc_msgSendSuper2函数函数传递至少两个参数,第一个参数是一个objc_super2类型的结构体,内部有两个成员,第一个成员是当前实例对象本身receiver,第二个成员是当前实例对象对应的类对象current_class。
- 当调用[super xxxx]方法方法时,其实不是给receiver的superclass发送消息,而是给当前的receiver发送消息。但是在执行消息查找时,会首先拿到current_class的superclass,然后到superclass的方法缓存和方法列表中查询方法。
- 也就是说,objc_super2的第一个成员变量receiver决定了谁是真正的消息接收者,而第二个成员变量current_class的superclass其实就决定了当前消息从哪里开始进行查找。
一般情况下,给对象receiver发送一个消息,首先会到receiver的缓存列表或者方法列表中去查找,找不到才会到superclass中去查找,而super则表示首先会给receiver发送一个消息,但是会先到recever的superclass中进行方法查找。这就是为什么使用super会调用父类方法。
Demo解析
首先创建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
- [self class],通过self(当前XLTeacher的实例对象)调用class方法其实就是给self发送一条@selector(class)消息,由于XLTeacher的实例对象以及它的父类都没有实现class方法,所以最终会查找到基类NSObject的方法列表中,最终找到方法实现,返回object_getClass(self);,也就是当前类对象XLTeacher。
- [super class],其实也是向self发送一条@selector(class)消息,只不过会先到XLTea的父类中去查找方法实现,由于父类没有实现class方法,所以最终找到基类NSObject的class方法,返回object_getClass(self);,结果也是XLTeacher。
- [self superclass],向self发送一条@selector(superclass),首先会在XLTeacher的缓存和方法列表中查找,XLTeacher没有实现此方法,因此会到XLTeacher的superclass中查找,父类也未实现,最后会找到NSObject中的superclass方法,返回[self class]->superclass,也就是XLPerson。
- [super superclass],利用super调用superclass方法,其实还是向self发送@selector(superclass)消息,但是会直接先从XLTeacher的父类XLPerson中查找方法实现,由于XLPerson未实现此方法,最终会调用NSObject的superclass方法,返回[self class]->superclass,也就是XLPerson。
isKindOfClass和isMemberOfClass
源码解析
首先,查看源码中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
- 类方法+isMemberOfClass:是通过isa获取当前类对象的元类对象,与参数中的对象进行对比,如果相等则返回YES。
- 对象方法-isMemberOfClass:是通过isa获取当前实例对象的类对象,与参数中的对象进行对比,如果相等,则返回YES。
- 类方法+isKindOfClass:通过遍历当前类对象所对应的元类对象以及父类所对应的元类对象,如果遍历到的元类对象与参数中的对象相等,则返回YES。
- 对象方法-isKindOfClass:通过遍历当前实例对象对应的类对象以及它的父类,如果遍历到的类对象和参数中的对象相等,则返回YES。
Demo解析
创建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;
}
类方法
- [NSObject isMemberOfClass:[NSObject class]]会返回NO,原因是object_getClass(NSObject)拿到的是元类对象,而[NSObject class]是类对象,两者不相等。
- [NSObject isKindOfClass:[NSObject class]]会返回YES,原本获取NSObject的元类对象和[NSObject class]来进行对比,应该是不相等,但是比较特殊的一点是NSObject的元类对象的superclass指向它的类对象,所以此处返回YES。此处可以参考Objective-C基础之一(深入理解OC对象)中isa和superclass的总结。
- [XLPerson isMemberOfClass:[XLPerson class]]会返回NO,获取到XLPerson的元类对象和[XLPerson class]进行比较,两者不相等。
- [XLPerson isKindOfClass:[XLPerson class]]会返回NO,首先会拿到XLPerson的元类对象和[XLPerson class]对比,发现不相等,然后会通过XLPerson的superclass拿到父类的元类和[XLPerson class]进行对比,发现还是不相等,一直遍历到基类NSObject的元类,NSObject的元类的superclass指向NSObject的类对象,因此最后一次遍历是拿NSObject的类对象和[XLPerson class]进行比较,肯定不相等,所以返回NO。
实例方法
- [person isMemberOfClass:[NSObject class]]返回NO,原因是拿person的类对象和NSObject比较,显然不相等。
- [person isKindOfClass:[NSObject class]]返回YES,因为XLPerson原本就是继承自NSObject,而isKindOfClass则是通过继承链最终能找到NSObject的类对象和参数[NSObject class]进行对比,两者相等。
- [person isMemberOfClass:[XLPerson class]]返回YES,person的类对象就是XLPerson。
- [person isKindOfClass:[XLPerson class]]返回YES,原因同上。
Runtime应用
使用runtime查看私有成员变量
利用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的实现。
runtime之方法交换(Method Swizzing)
之前的文章中说到过,类的所有对象方法都是存放在类对象的方法列表中,也就是存放在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。
runtime常用Api
动态类
//动态创建一个类(参数:父类,类名,额外的内存空间)
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)