Runtime(六). superClass的本质

546 阅读5分钟

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里找classsuperclass方法

猜测classsuperclass实现:


- (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的元件)