1. load方法的调用时机?
在load_images
函数中
add_class_to_loadable_list
:将所有主类的load
方法,收集到一张主类load
方法表add_category_to_loadable_list
:将所有分类的load
方法,收集到一张分类load
方法表call_class_loads
:先循环调用主类的load
方法call_category_loads
:再循环调用分类的load
方法
1.1 add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
- 将所有主类的
load
方法,收集到一张主类load
方法表
1.2 add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
add_category_to_loadable_list
:将所有分类的load
方法,收集到一张分类load
方法表
1.3 call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
call_class_loads
:先循环调用主类的load
方法
1.4 call_category_loads
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, @selector(load));
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
call_category_loads
:再循环调用分类的load
方法
2. 主类方法和分类方法的调用顺序?
2.1 普通方法
普通方法,包括initialize
,优先分类中的方法调用
因为分类的方法是在类realize
之后attach
进去的,所以插在前面
2.2 load方法
load
方法,优先主类,然后分类
因为类的初始化,优先主类,读取ro
。然后分类初始化,读取rwe
2.3 多分类
多个分类之间,看文件的编译顺序。load
方法,先编译的分类先执行。同名方法,最后编译的分类中的方法会被执行
3. load、initialize、cxx的调用顺序?
load
方法和cxx
函数,在程序启动时自动调用
调用顺序:load
→cxx
→main
对于相同镜像文件,load
方法一定在cxx
函数之前
不同镜像文件的调用顺序:系统库优先→动态库→主程序
initialize
方法,属于懒加载方法,在对象首次消息发送时调用
objc
中的cxx
函数,它会在_objc_init
函数中,调用static_init
函数,执行C++
静态构造函数
4. Runtime是什么?
Runtime
是由C
、C++
、汇编实现的一套API
,为OC
语言增加面向对象及运行时的功能
Runtime
是指将数据类型的确定从编译时推迟到运行时
例如:类扩展(Extension
)和分类(Category
)的区别
Runtime
机制对于AOP
面向切面编程提供良好的支持
平时编写的OC
代码,最终都会转换成Runtime
的C
语言代码,Runtime
是Object-C
的幕后工作者
5. 方法的本质是什么?
方法的本质是消息发送,消息发送的流程:
- 消息快速查找,由汇编代码实现的
objc_msgSend
- 消息慢速查找,遍历当前类和父类,由
C++
代码实现的lookUpImpOrForward
函数 - 方法动态决议,
resolveInstanceMethod
- 消息快速转发,
forwardingTargetForSelector
- 消息慢速转发,
methodSignatureForSelector
、forwardInvocation
- 挽救失败,由
doesNotRecognizeSelector:
报出异常
5.1 sel
和imp
sel
:方法编号,在read_images
时就读取到了内存imp
:函数地址,消息发送就是sel
找imp
的过程
5.2 二者的关系
sel
为方法编号,imp
为sel
对应的函数地址,通过imp
函数地址才能找到真正的函数实现
例如
sel
相当于一本书的目录imp
相当于页码,通过imp
才能找到函数的实现代码
查找某个函数的过程,相当于找到这本书中某个章节的具体内存
- 首先知道要找的是什么,
sel
- 找到对应的页面,
imp
- 将书翻到指定页,找到具体内容
6. 能否在运行时对编译后的类添加实例变量?
不能,因为编译后的实例变量存储在ro
中,⼀旦编译完成,内存结构就完全确定了,⽆法修改。可以对编译后的类添加属性和⽅法
7、能否对运⾏时创建的类添加实例变量?
可以,使用objc_allocateClassPair
创建类,只要在objc_registerClassPair
注册之前,可添加实例变量。一旦注册后,无法添加实例变量
使用class_addIvar
添加实例变量,添加前进行flags & RW_CONSTRUCTING
的条件判断
BOOL
class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *type)
{
...
// No class variables
if (cls->isMetaClass()) {
return NO;
}
// Can only add ivars to in-construction classes.
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO;
}
...
}
调用objc_registerClassPair
函数,会对flags
进行标记
void objc_registerClassPair(Class cls)
{
...
// Clear "under construction" bit, set "done constructing" bit
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
...
}
8. [self class]和[super class]的区别?
[self class]
:其中self
为参数名,本质调用objc_msgSend
,消息接收者为self
,方法编号为class
[super class]
:其中super
为关键字,本质调用objc_msgSendSuper
,消息接收者为self
,方法编号为class
。运行时,查看汇编代码,实际上调用的objc_msgSendSuper2
8.1 案例分析
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@end
@implementation LGPerson
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"init:%@ - %@",[self class],[super class]);
}
return self;
}
@end
-------------------------
init:LGPerson - LGPerson
[self class]
和[super class]
,二者的打印结果都是LGPerson
因为二者的消息接收者都是当前self
,而方法编号都是class
而二者的区别,self
调用objc_msgSend
,super
调用objc_msgSendSuper
它们最终调用的都是NSObject
中的class
方法,消息接收者都是self
当前实例对象,只是objc_msgSendSuper
直接查找父类方法,比使用objc_msgSend
更快
8.2 [self class]
的本质
(Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))
8.3 [super class]
的本质
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"))
8.4 __rw_objc_super
结构体
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
8.5 objc_msgSendSuper2
在objc
源码中,打开objc-msg-arm64.s
文件
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
运行时,实际调用的是objc_msgSendSuper2
,目的是objc_msgSendSuper
升级后的过度
9. 实例方法的调用?
9.1 实例方法的两种调用方式
创建LGPerson
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
- (void)say1;
@end
@implementation LGPerson
- (void)say1{
NSLog(@"%@ : %s",self,__func__);
}
@end
在viewDidLoad
方法中,使用两种不同方式,对其进行调用
- (void)viewDidLoad {
[super viewDidLoad];
//方式一
LGPerson *p = [[LGPerson alloc] init];
[p say1];
//方式二
Class cls = [LGPerson class];
void *ptr = &cls;
[(__bridge id)ptr say1];
}
-------------------------
<LGPerson: 0x281e546d0> : -[LGPerson say1]
<LGPerson: 0x16b141aa0> : -[LGPerson say1]
两种方式全部调用成功
方式一,常规写法,调用实例对象的say1
方法,进入objc_msgSend
流程,拿到实例对象所属isa
,然后进行方法查找
方式二,将类对象地址,赋值给ptr
指针,然后将其桥接成OC
的id
类型,进行say1
方法的调用。ptr
指针等同于实例对象所属isa
,然后进行方法查找,所以也能调用成功
9.2 实例方法中打印属性
修改say1
方法
- (void)say1{
NSLog(@"%@ : %s : %@",self,__func__,self.lgName);
}
在main
函数中,使用两种不同方式,对其进行调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
//方式一
LGPerson *p = [[LGPerson alloc] init];
[p say1];
//方式二
Class cls = [LGPerson class];
void *ptr = &cls;
[(__bridge id)ptr say1];
}
return 0;
}
-------------------------
<LGPerson: 0x281e546d0> : -[LGPerson say1] : (null)
<LGPerson: 0x16b141aa0> : -[LGPerson say1] : <LGPerson: 0x281e546d0>
两种方式,打印出lgName
的属性值各不相同。实例对象与类对象最大的区别,实例对象使用malloc
开辟内存空间,结构中包含isa + 成员变量
。所以实例对象使用首地址 + 偏移值
的方式,在自己开辟的堆空间中,偏移isa
指针的8字节
,即可找到lgName
属性并对其打印
方式一,由于lgName
没有赋值,打印结果为null
方式二,ptr
为类对象的指针,在viewDidLoad
的函数调用栈中,所以ptr
使用首地址 + 偏移值
的方式,只能找到函数调用栈中ptr
的上一个元素,即:LGPerson
的实例对象p
9.3 为什么上一个元素是LGPerson
的实例对象?
栈:是一种具有特殊的访问方式的存储空间,具有后进先出的特性(Last In Out Firt,LIFO
)
在ARM64
中,栈的开口方向是向下的,由高地址到低地址。对栈的操作是16字节
对齐
打印函数调用栈中的全部元素
void *fp = (void *)&self;
void *sp = ptr;
long count = (fp - sp) / 0x8;
for (long i = 0; i <= count; i++) {
void *address = fp - i * 0x8;
if(i==1){
NSLog(@"%p : %s",address, *(char **)address);
continue;
}
NSLog(@"%p : %@",address, *(void **)address);
}
-------------------------
0x16dd5dac8 : <ViewController: 0x102607340>
0x16dd5dac0 : viewDidLoad
0x16dd5dab8 : ViewController
0x16dd5dab0 : <ViewController: 0x102607340>
0x16dd5daa8 : <LGPerson: 0x282808ca0>
0x16dd5daa0 : LGPerson
允许压栈进来的对象,包含方法的参数,
viewDidLoad
方法的两个隐式参数self
和_cmd
调用
[super viewDidLoad]
方法,需要在当前函数调用栈中,创建__rw_objc_super
结构体的临时变量,然后传入super
方法。结构体的成员变量,分别包含object
和superClass
方法中定义的局部变量,例如:
LGPerson
的实例变量p
,LGPerson
类对象的ptr
指针
从栈底到栈顶,每8字节
打印一个元素。最后一个元素ptr
指针,使用ptr + 8字节
偏移,找到的是LGPerson
的实例对象p
9.4 为什么superClass
传入的是ViewController
?
查看[super viewDidLoad]
方法的汇编代码
x0
寄存器中存储结构体,其中super_class
成员变量,存储的不是父类UIViewController
,而是当前类对象ViewController
在objc
源码中,搜索objc_msgSendSuper2
#if __OBJC2__
// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
- 注释:接受当前的搜索类,而不是它的父类
查看汇编的源码
cls
为要搜索的类的子类
10. Runtime是如何实现weak的,为什么可以⾃动置nil?
- 通过
SideTable
找到我们的weak_table
weak_table
根据referent
找到或者创建weak_entry_t
- 然后
append_referrer(entry, referrer)
将我的新弱引⽤的对象加进去entry
- 最后
weak_entry_insert
把entry
加⼊到我们的weak_table