iOS基础概念2
+load
调用时机:
加载镜像load_images中的类对象(class)和分类(category)的时候就会调用
void load_images(xxx) {
xxxx
//1. 查找并准备load方法,以供后面去调用
prepare_load_methods((const headerType *)mh);
call_load_methods(); // 2.调用这些load方法
}
prepare_load
递归方法把 类及其父类 add_class_to_loadable_list(cls);
同样按编译顺序添加分类 add_category_to_loadable_list(cat)
最终需要调用load方法的类就都添加到列表里了,等待被遍历获取
调用频率:
只会调用一次,不论在项目中有没有用到该类对象或者该分类,所有的类都会调
用一次
调用顺序:
1 先调用类对象(class)的+load方法:
1.1类对象的load调用顺序是按照 类文件的编译顺序 进行先后调用;
1.2调用子类+load之前会先调用父类的+load方法(递归寻找存储在列表
里)
2 再调用分类(category)的+load方法:
按照编译先后顺序调用(先编译的,先被调用)
在main函数开始之前调用load方法
调用机制:
通过函数指针load_method从loadable_class中获得+load方法的IMP作为其参
数,然后就直接对其进行调用(*load_method)(cls, SEL_load);
load 方法不是通过消息机制调用的, 而是获取IMP直接调用的,因此不会存在方
法被覆盖问题
+initialize
调用时机:
在类第一次接收到消息的时候调用
调用机制:
通过objc_msgSend()进行调用的
通过消息机制调用的方法 1 可能被调用多次 ,2 可能被分类重名导致“覆盖”,初
始化分类最后编译的那个(如果实现了的话)
可使用dispath_once 保证执行一次
调用顺序:
1.先调用父类的,再调用子类的
2.如果子类没实现+initialize方法,会调用父类的+initialize。所以父类的
+initialize可能被调用多次
3.如果分类实现了+initialize,会覆盖类本身的+initialize
代码逻辑:
void initializeNonMetaClass(Class cls) {
ASSERT(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
// 1. 递归调用,父类的initialize在子类之前调用
initializeNonMetaClass(supercls);
}
...省略部分代码...
{
// 2. 调用initialize方法
callInitialize(cls);
....
}
...
}
void callInitialize(Class cls) {
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
// 可以看到callInitialize中直接通过objc_msgSend方法调用+initialize方法。
这也就解释了为什么+initialize子类没实现,会去调用父类中的。category中的
会覆盖类本身的。
isa 结构
32位 旧版 isa 结构:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
对比:
32位和64位对比发现,新版isa不单单是指向对象的地址指针
64位isa结构:
struct objc_object {
private:
isa_t isa;
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
// 0,代表普通指针,存储着class、meta-class对象的内存地址;1,代
表优化过,使用位域存储更多信息
uintptr_t nonpointer : 1;
// 是否设置过关联对象,如果没有,释放时会速度更快
uintptr_t has_assoc : 1;
// 是否有C++的稀构函数,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1;
// 这个部分存储的是真正的Class、Meta-Class对象的内存地址信息,因
此要通过 isa & ISA_MASK才能取出这里33位的值,得到对象的真正地址。
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS
0x1000000000
// 用于在调试的时候分辨对象是否完成了初始化
uintptr_t magic : 6;
// 是否被弱饮用指针指向过,如果没有,释放时会更快
uintptr_t weakly_referenced : 1;
// 对象是否正在被释放
uintptr_t deallocating : 1;
// 引用计数器是否过大无法存储在isa中,若果是,这里就为1,引用计数
就会被存储在一个叫SideTable的类的属性中。
uintptr_t has_sidetable_rc : 1;
// 里面存储的值是 引用计数 - 1
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__ 等等其它内核
MASK
isa指针需要通过与一个叫ISA_MASK的值(掩码)进行二进制&运算,才能得到
真实的class/meta-class对象的地址
通过 isa & ISA_MASK 取出来的到底是哪几位上面的值。同时还可以发现一个小
细节,最终得出来的对象的地址值,会得到36个有效二进制位,而最后的四位,
只可能是 1000 或者 0000,也就是十六进制下的 8或0,因此对象的地址最后
一位(十六进制下),一定是8或0。
内存布局
代码段 :占用空间很小,一般存放在内存的低地址空间,我们平时编写的所有代
码,就是放在这个区域
数据段 :用来存放全局变量
堆区 :是动态分配内存的,用来存放我们代码中通过alloc生成的对象,动态分配
内存的特点是需要程序员申请内存和管理内存。例如OC中alloc生成的对象需要
调用releas方法释放【MRC下】,C中通过malloc生成的对象必须要通过free()去
释放。
栈区 :系统自动分配和销毁内存,用于存放函数内生成的局部变量