demo
#import "MJStudent.h"
#import <objc/runtime.h>
@implementation MJStudent
/*
[super message]的底层实现
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现
*/
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};
- (void)run
{
// super调用的receiver仍然是MJStudent对象
[super run];
// struct objc_super arg = {self, [MJPerson class]};
//
// objc_msgSendSuper(arg, @selector(run));
//
//
// NSLog(@"MJStudet.......");
}
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // MJStudent
NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
NSLog(@"[super class] = %@", [super class]); // MJStudent
NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
}
return self;
}
@end
//@implementation NSObject
//
//- (Class)class
//{
// return object_getClass(self);
//}
//
//- (Class)superclass
//{
// return class_getSuperclass(object_getClass(self));
//}
//
//@end
把这个MJStudent.m 转化为 cpp. 可知(搜索MJStudent_run)
[super run]源码为
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJStudent"))}, sel_registerName("run"));
简化为
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};
struct objc_super arg = {self, [MJPerson class]};
objc_msgSendSuper(arg, @selector(run));
-
调
objc_msgSendSuper.第一个参数为一个结构体,结构体第一成员是接收者,第二个成员是父类类对象。 -
意思是:
1. 接收者仍然是子类对象。 2. 从父类开始查找方法的实现。 -
所以
[super class]打印结果是 MJStudent;[super superclass]打印结果是MJPerson. 最后都是在NSObject里找class和superclass方法
猜测class和superclass实现:
- (Class)class
{
return object_getClass(self);
}
- (Class)superclass
{
return class_getSuperclass(object_getClass(self));
}
LLVM中间码
从转为C++代码可以,super是调用了objc_msgSendSuper这个方法。但是转成的C++尽管和最终的源码差不多,但是还是有出入。
实质上是调用objc_msgSendSuper2方法,传入的是当前类对象,不是父类类对象。但是本质上是不变的,还是从父类开始往上找。
方法一:打断点看汇编。
方法二:其他方法看汇编
xcode: Product --> Perform Action --> Assemble "当前.m" 可以显示LLVM中间码
因为该行代码在25行,搜索 ":25"
方法三: LLVM中间码
代码经过LLVM前端,转成LLVM中间码,这个中间码是用来跨平台的,任何平台都一样,然后再转为跟机器和CPU,平台有关的汇编或机器码。
可见调用的是 objc_msgSendSuper2.
- 可以用
clang -emit-llvm -S main.m生成LLVM中间码 - LLVM具体可以参考官方文档 llvm.org/docs/LangRe…
isKindOfClass 和isMemberOfClass
NSObject的一些方法是开源的(并不是所有的Foundation都是闭源的)
objc-723里搜索:
NSObject.mm
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
demo
NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]); // 0
// 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
// id person = [[MJPerson alloc] init];
//
// NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]); // 1
// NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); // 0
//
// NSLog(@"%d", [person isKindOfClass:[MJPerson class]]); // 1
// NSLog(@"%d", [person isKindOfClass:[NSObject class]]); // 1
//
//
// NSLog(@"%d", [MJPerson isMemberOfClass:object_getClass([MJPerson class])]); //1
// NSLog(@"%d", [MJPerson isKindOfClass:object_getClass([NSObject class])]); // 1
//
// NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]); // 1
-isMemberOfClass当前对象的类对象,是不是传进来的Class对象+isMemberOfClass当前的元类对象是不是传进来的class对象-isKindOfClass当前对象的类对象是否是传进来的类对象,或者是否子类的类对象+isKindOfClass当前的类对象,是否是传进来的元类对象,或者是其子类对象的元类对象- 所以
+isMemberOfClass,+isKindOfClass参数必须是元类对象,特殊的是下面这种情况。
注意:`NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]); `打印结果是1.因为NSObject的元类的superClass指针指向NSObject的类对象。
一道面试题
/*
1.print为什么能够调用成功?
2.为什么self.name变成了ViewController等其他内容
*/
- (void)viewDidLoad {
[super viewDidLoad];
// struct abc = {
// self,
// [ViewController class]
// };
// objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));
// NSObject *obj2 = [[NSObject alloc] init];
//
// NSString *test = @"123";
// int age = 10; 崩溃
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
long long *p = (long long *)obj;
NSLog(@"%p",p);
NSLog(@"%p",*p);
NSLog(@"%p %p", *(p+2), [ViewController class]);
// struct MJPerson_IMPL
// {
// Class isa;
// NSString *_name;
// };
}
分析:
- cls就相当于person的isa指针,obj就相当于person对象,所以可以给obj发送print方法。
打印结果:
my name is <ViewController: 0x7fda42f07a80>
- 栈空间是从高往低分配。cls和obj都在栈空间,cls在高地址,obj在低地址。
- print方法打印的是当前对象的属性。也就是对象isa+1的指针内容。
- obj相当于person对象,cls相当于isa指针。所以打印的是cls+1的指针内容。
- 如果cls初始化的前面
NSString *test = @"123";打印的就是"123"。 - 又由于
[super viewDidLoad];的存在。由上面可知,super的本质是调用objc_msgSendSuper2,生成了一个结构体局部变量,作为第一个参数传进去。所以cls上面是个结构体。 - 又知道结构体成员是从低地址到高地址分配。
- 所以本地打印的是结构体,结构体地址和其第一个成员地址一样,所以本题打印的是当前控制器对象(self). self是在栈中,指向ViewController对象
<ViewController: 0x7fda42f07a80>
long long *p = (long long *)obj;
NSLog(@"%p",p);
NSLog(@"%p",*p);
NSLog(@"%p %p", *(p+2), [ViewController class]);
- 对obj进行强转化为p.
NSLog(@"%p",p);打印的是obj里存的内容,也就是cls的地址。通过p/x obj可验证。NSLog(@"%p",*p);打印的是p(obj)里放的是cls,所以*p是cls所指向的内存地址。也就是cls的内容。NSLog(@"%p %p", *(p+2), [ViewController class]);.*(p+2):p的内容是cls的地址,cls的地址往高位移2个单位是ViewController的类对象。x/4g 0x00007ffeed8754a8,0x00007ffeed8754a8是cls地址。该命令打印的是:从0x00007ffeed8754a8开始,4个8字节内存里放的是什么。第一个8字节放的是0x000000010238a070是[MJPerson class],第二个8字节是当前控制器self;第三个8字节是[ViewController class.验证了上面的。
注意:如果cls初始化上面是代码:int age = 10;会崩溃。
-
- (void)viewDidLoad 的本质为
void viewDidLoad(id self,SEL _cmd){
}
self和_cmd这两个隐式参数,一般会放在栈空间,但是arm64后,一般会放在寄存器里。(寄存器是CPU的元件)