load_Images方法
load_Images方法在dyld调起以后,通过objc_init发起,在notifyRegister回调中调起,作为第二个参数,在map_images之后,也就是初始化镜像文件以后。进来以后,通过递归搜索,匹配关键字load找到所有的load方法,递归条件是一层一层找父类,直到nil跳出,每个方法找到以后会标记rw,找到方法以后会放到一张表里面,表里面是作为键值对存储,有两对,一个是类class,一个是method方法,先收集类,在收集分类,收集完毕以后,再作load调用处理。除此以外,init方法在是在第一次消息发送的时候调用,也就是说在load方法之后调用,还有一个自发调用的c++构造函数,这个方法在load之后,init之前。如果c++方法在objc中,那么先调用c++方法,再调用load,发起者是doinit~,比notifyRegister更早。
SEL是什么?IMP是什么?
SEL是方法编号,imp是函数指针地址.通过方法编号去查找对应的imp
能够向编译后的类中添加实例变量?能否向运行时创建的类添加实例变量?
不能向编译后的类中添加,因为在编译后类的内存结构已经固定了,不能改变。
【self class】和 【super class】
class方法在我们自己类中并没有,那么系统就会一直往上找,直到找到NSObject,在NSObject中找到了方法的实现。这个self的意思是作为sel的参数,也就是方法的接收者,那么这个方法归根结底的接收者,是我们的本类,而不是NSObject,在底层发送消息通过msgSend,识别来自消息的接收者是本类,只不过它是不停的往上查找直到查到NSObject中而已。
- (Class) class {
return object_getClass(self);
}
super是关键字,接收者仍然是本类,只不过他默认是从父类开始查找。self是形参名字。那么为什么初始化要这么写呢?如果写【self init】,那么程序就会陷入死循环然后崩溃,因为他会一直在本类中查找跳不出去了。super的底层调用首先会通过objc_msgSendSuper,这个objc_msgSendSuper是传入了一个结构体,结构体内部是包括接收者和父类。比如我们在自定义cell的时候,初始化构造默认要返回instancetype,然后才可以实现子类的自定义。
objc_msgSendSuper(**struct** objc_super * **_Nonnull** **super**, **SEL** **_Nonnull** op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/// Specifies the superclass of an instance.
**struct** objc_super {
/// Specifies an instance of a class.
**__unsafe_unretained** **_Nonnull** **id** receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
**__unsafe_unretained** **_Nonnull** Class **class**;
#else
**__unsafe_unretained** **_Nonnull** Class super_class;
#endif
/* super_class is the first class to search */
};
下面的第二句say会不会报错?
NXPerson *p = [[NXPerson alloc]init];
[p say];
Class per = [NXPerson class];
void *newP = &per;
[(__bridge id)newP say];
不会报错并且可以打印出say的内容。
原因:获取了类的地址给了newP,只要能够拿到类的地址,就可以查到类的方法,从而调用方法,不管你是对象还是其他,调起方法。person能够给方法发送消息也是因为通过isa找到这个类,isa里面就有类地址。只不过person是一个实例对象,他有自己的内存空间,而newP只是一个类的地址。
压栈测试
在之前我们就知道,如果在类中声明一个属性,要想要获取到它的值,系统是通过首地址平移到这个属性所在的内存地址的,然后获取他的值。
看一个例子:在类中声明一个属性,没有赋值,在类的实现方法中打印这个属性,显然没有赋值的话,正常对象调用方法打印是nil,如果是任意一个类的指针地址去调用呢?他也会平移,只不过此时它不像对象那样存在一块内存区域,那么平移会打印什么呢?这就需要我们做一些"深入"的探索~~~
分别打印p的地址和per的地址,不难发现中间差8字节,正好是这个对象的大小,中间不存在压栈。分析地址的大小,栈中是先进后出,越往下越大,也就是先class再person,那么为什么打印的是ViewController呢?我尝试了一下,如果在main中,打印的是AppDelegate。
结构体压栈,遵循在栈中是先进后出,打印地址也可以看到,p的地址是最下面的也就是最大的,那么结构体内部呢?通过对对象的地址往前平移8,打印出的值是3也就是num2的值,说明在结构体内部,他的排序是正常正序排序的。我们知道在
super关键字内部的结构体首先是self也就是那个receiver,第二个是superClass,那么往前平移就是这个class也就是他的发起者也就是他自己ViewController。
参数压栈,与结构体相反。p1先压,p2后压。
压栈的原理
只有临时变量和参数才会压栈,像[super viewDidLoad]他的压栈在super调用的方法里面,只会压栈当前的函数栈里面。super调用会产生临时变量结构体,在内部进行压栈。
为什么打印的是ViewController而不是UIViewController?
官方注释是说返回的是本类,也就是ViewController,而不是UIViewController,通过打印寄存器的内容也可以看出来。