程序员社区

OC-Runtime-super面试题相关

OC-Runtime-super面试题相关插图
面试题
***********************?MJPerson.h ?**************************
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;

- (void)print;
@end

***********************?MJPerson.m ?**************************  
#import "MJPerson.h"

@implementation MJPerson

- (void)print
{
    NSLog(@"my name is %@", self->_name);
}
@end

@implementation ViewController

/*
 1.print为什么能够调用成功?
 
 2.为什么self.name变成了ViewController等其他内容
 */

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    id cls = [MJPerson class];

    void *obj = &cls;

    [(__bridge id)obj print];
    
}
@end

RUN>

*********************** ?运行结果? **************************
2021-05-08 16:03:34.586222+0800 Interview02-super[4242:166676] my name is <ViewController: 0x7fb47340ecf0>

1.print为什么能够调用成功?

2.为什么self.name变成了ViewController等其他内容

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *test = @"123";
    id cls = [MJPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
}

RUN>

*********************** ?运行结果? **************************
2021-05-08 16:05:32.180416+0800 Interview02-super[4288:170283] my name is 123
OC-Runtime-super面试题相关插图1
image-20210508160938861

很奇怪,怎么会这样呢? 首先第一点是为什么会调用到MJPerson- (void)print方法,第二,局部变量 NSString *test = @"123";怎么就变成了person的实例成员。

- (void)viewDidLoad {
    [super viewDidLoad];
    //调用print
    MJPerson *person = [[MJPerson alloc]init];
    [person print];
}

RUN>

*********************** ?运行结果? **************************
2021-05-09 19:55:04.568600+0800 Interview02-super[2315:98649] my name is (null)    
OC-Runtime-super面试题相关插图2
image-20210509200146018
 id cls = [MJPerson class];
 void *obj = &cls;
 [(__bridge id)obj print];

这段代码在内存结构上发生了什么情况

OC-Runtime-super面试题相关插图3
image-20210509201257058

[(__bridge id)obj print]; 调用了能够调用到MJPerson- (void)print方法。那我们把obj看出person的instance会是什么情况

OC-Runtime-super面试题相关插图4
image-20210509202016984

他们的内存结构何其相似.cls在这里不就等价于isa么?他们都指向 MJPerson类对象.

[person print];通过实例对象 personisa指针找到MJPerson 类对象,然后从类对象的方法列表中查找方法.所以,方法的调用本质就是只要能找到类对象.而[(__bridge id)obj print];,指针变量obj中存储的cls恰巧就指向类对象,当消息系统objc_msgSend把obj当成类了,在他所指向的地址,获取前8位地址作为类的isa指针,指向了MJPerson 类对象 ,所以能够当成MJPerson类,所以最后能调用成功.

接着分析 my name is 123 这个局部变量 NSString *test = @"123";怎么就变成了person的实例成员。

// 局部变量分配在栈空间
// 栈空间分配,从高地址到低地址
void test()
{
    long long a = 4; // 0x7ffee638bff8
    long long b = 5; // 0x7ffee638bff0
    long long c = 6; // 0x7ffee638bfe8
    long long d = 7; // 0x7ffee638bfe0
    
    NSLog(@"%p %p %p %p", &a, &b, &c, &d);
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        test();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

RUN>

*********************** ?运行结果? **************************
2021-05-09 20:30:17.556720+0800 Interview02-super[2557:120622] 0x7ffeed6a7c28 0x7ffeed6a7c20 0x7ffeed6a7c18 0x7ffeed6a7c10

从打印结果中可以看到,栈内存分配空间是从高地址往低地址分配的,<font color='red'>先创建的局部变量分配在高地址,后创建的分配在低地址</font>.

NSString *test = @"123";
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];

继续查看这段代码的内存结构

OC-Runtime-super面试题相关插图5
image-20210509203828027

上面我们已经分析过,把cla看出isa指针,isa指针后面8个地址就是_name 属性指向地址。在获取self.name的时候,如果转换成汇编代码会发现本质上就是找到isa指针,然后越过8个字节,从而找到_name.这就是内存访问的本质:找到某块内存的地址,读取地址中的值.

[(__bridge id)obj print]; 也会同样的越过cls这8个字节找到test.所以最后就打印的是my name is 123;

super 关键字的一点补充

在前面讲super关键字的时候,我们看到super转换为c++代码的时候,被转换成了objc_msgSendSuper(arg1,arg2)函数.其实实际上底层执行并不是objc_msgSendSuper(arg1,arg2)函数,而是objc_msgSendSuper2(arg1,arg2)函数.我们在[super viewDidLoad];处打个断点,然后显示汇编语言看一下:

OC-Runtime-super面试题相关插图6
image-20210509205809559

并且上面讲的objc_msgSendSuper(arg1,arg2)中的第一个参数arg1__rw_objc_super结构体,这个结构体如下:

struct __rw_objc_super { 
    struct objc_object *object; 
    struct objc_object *superClass; 
}

objc_super2的结构体如下:

struct objc_super2 {
    id receiver;
    Class current_class;
};

可以看到objc_super2这个结构体中传入的是Class current_class;也就是当前类.而在_objc_msgSendSuper2内部获取当前类的superClass:

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame
    MESSENGER_START

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
    CacheLookup NORMAL

    END_ENTRY _objc_msgSendSuper2

    
    ENTRY _objc_msgLookupSuper2
    UNWIND _objc_msgLookupSuper2, NoFrame

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
    CacheLookup LOOKUP

    END_ENTRY _objc_msgLookupSuper2
OC-Runtime-super面试题相关插图7
image-20210509210422437
OC-Runtime-super面试题相关插图8
image-20210509211303627

所以,super底层其实是调用objc_msgSendSuper2()函数,然后传入的是当前类对象,只不过在内部又回取出当前类对象的superclass.
这只是一个小细节,和我们最开头说的也不矛盾,知悉就好.

特别备注

本系列文章总结自MJ老师在腾讯课堂iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!

赞(0) 打赏
未经允许不得转载:IDEA激活码 » OC-Runtime-super面试题相关

一个分享Java & Python知识的社区