小码哥iOS学习笔记第十五天: super面试题

1、面试题

  • 建立OC项目, 定义Person类, 添加name属性和实现print方法, 以下图所示

  • ViewController中的-viewDidLoad方法里实现下面的代码

问: 代码是否会报错? 是否可以顺利运行? 是否能打印?, 若是能, 会打印什么?面试

  • 运行程序, 能够看到程序没有报错, 且正常运行, 并有以下打印

Person name is <ViewController: 0x7ff78ef08880>
复制代码

问: 为何这么打印?bash

2、解析

  • 现有以下代码

  • person是一个指针, 存储着[[Person alloc] init]的地址, 因此person指向刚建立的Person实例对象

  • 咱们知道, 一个对象在底层就是一个结构体, 它的第一个成员变量是isa, 因此[[Person alloc] init]在底层就是下面的样子

  • 由于isaPerson对象中的第一个成员变量, 因此isa的地址与[[Person alloc] init]的地址相同

  • person调用-print方法时, 本质是经过isa找到[Person class]对象, 而后找到-print方法来调用, 因此指针关系图以下

  • -print方法中, 有打印成员变量_name, 因此须要经过[[Person alloc] init]找到_name存储的值
  • 在底层是经过isa的地址 + 8找到_name的地址, 而后访问_name存储的内容
  • 这样就能够顺利打印了

obj能调用print方法的缘由

  • 接着咱们看回面试题中的代码
id cls = [Person class];
复制代码
  • 这一句代码表示cls指针指向Person的类对象

  • 接着下一句, obj存储着cls的地址
void *obj = &cls;
复制代码
  • 也就是说obj指向cls

  • 这个指向和上面的person指向相似, 有下面这种指针结构

  • 最后看调用方法这一句
[(__bridge id)obj print];
复制代码
  • 在结构上ui

    • person调用方法的流程是: person->isa->[Person class]
    • obj调用的流程与person相似. obj->cls->[Person class]
  • 这就是obj可以调用-print方法的缘由spa

局部变量在栈中的排列顺序

  • 定义三个局部变量, 查看他们的地址

  • 三个局部变量的地址
0x7ffeeec0c9d8
0x7ffeeec0c9d0
0x7ffeeec0c9c8
复制代码
  • 能够看到, 连续的三个局部变量, 在栈中的存储顺序是连续的3d

  • 接着咱们看下面的这种状况, 在cls前面添加test变量, 运行看一下打印指针

  • 此时在内存中的排序以下

  • obj经过找到cls, 而后用cls的地址+8, 找到的就是test的地址, 因此打印时, 找到的_name就是test的值123

那么面试题中的打印为何是ViewController的实例对象呢?code

super的底层结构

  • 查看ViewController.cpp底层结构

  • 其中[super viewDidLoad];这一句在底层代码是下面这一句
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
复制代码
  • 整理后, 代码以下
objc_msgSendSuper({self, class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
复制代码
  • 也就是说, 在栈中有个结构体{self, class_getSuperclass(objc_getClass("ViewController"))}
  • 此时, 栈中的结构以下图所示

  • 因此此时经过cls的地址+8, 找到的就是self, 也就是当前控制器对象
相关文章
相关标签/搜索