内存对齐
1、数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3、收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员(基本数据类型)的整数倍.不足的要补⻬。
class_getInstanceSize(Class cls)方法, 查看一个对象中, 所有成员变量占用的内存大小, (内存对齐后的)
malloc_size(const void *ptr)方法, 方法查看操作系统实际分配的内存空间. 且一定是16的整数倍.
Type Encodings
v24@0:8@16的含义也就是:
- v:返回值void
- 24:参数一共占用24字节.
- @: 对象类型参数self
- 0:上面参数从0位置开始
- :: SEL
- 8:SEL从8位置开始
- @:对象类型,实际传入的第一个参数
- 16:从16位置开始
superClass 和 isa
- instance调用对象方法的轨迹
- isa找到class, 方法不存在, 就通过superclass找父类
- class调用类方法的轨迹
- isa找meta-class, 方法不存在, 就通过superclass找父类
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
- (void)test {
NSLog(@"- [NSObject test] - %p", self);
}
@end
[Person test];
源码
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef id (*IMP)(id, SEL, ...);
struct objc_object {
private:
isa_t isa;
}
union isa_t {
Class cls;
uintptr_t bits;
struct {
ISA_BITFIELD;
};
};
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; 表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址**,isa** 中包含了类信息、对象的引用计数等
uintptr_t has_assoc : 1; 关联对象标志位,0没有,1存在
uintptr_t has_cxx_dtor : 1;该对象是否有 C++ 或者 Objc 的析构器**,如果有析构函数,则需要做析构逻辑,** 如果没有**,**则可以更快的释放对象
uintptr_t shiftcls : 33;存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
uintptr_t magic : 6; 用于调试器判断当前对象是真的对象还是没有初始化的空间
uintptr_t weakly_referenced : 1;对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
uintptr_t deallocating : 1; 标志对象是否正在释放内存
uintptr_t has_sidetable_rc : 1; :当对象引用技术大于 10 时,则需要借用该变量存储进位
uintptr_t extra_rc : 19 当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
}
struct class_data_bits_t {
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
}
struct class_rw_t {
const method_array_t methods() const {}
const property_array_t properties() const {}
const protocol_array_t protocols() const {}
}
struct class_ro_t {
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties
}
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
union {
Ptr<List> list; // - list.ptr 中存储着 Element类型的数据
uintptr_t arrayAndFlag;
};
};
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
Element& get(uint32_t i) const { }
}
class method_array_t : public list_array_tt <method_t, method_list_t, method_list_t_authed_ptr>
class property_array_t : public list_array_tt<property_t, property_list_t, RawPtr>
class protocol_array_t : public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {}
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {};
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {}
struct property_t {
const char *name;
const char *attributes;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
}
struct method_t {
struct big {
SEL name;
const char *types;
MethodListIMP imp;
}
}
假设类对象的地址是 p
1. 那么 p + 0x20(isa 8 字节, superclass 8 字节, cache : 16字节, 共 32 = 0x20 字节) == bits地址;
2. bits->data () 拿到 class_rw_t* rw .
1. 属性
1. (*rw).properties() 返回 protocol_array_t protoclsArray;
2. protoclsArray.list 返回 RawPtr<property_list_t> rowPtr;
3. rowPtr.ptr 返回 property_list_t* list;
4. (*list).get(0) 返回property_t类型
property_t : (name = "name", attributes = "T@\"NSString\",C,N,V_name")
2. 实例方法
1. (*rw).methods() 返回 method_array_t methodsArray;
2. methodsArray.list 返回 method_list_t_authed_ptr<method_list_t>) authedPtr;
3. authedPtr.ptr 返回 method_list_t* list;
4. list.get(0) 返回method_t类型
5. method_t.big() 查看结构体
method_t::big: { name = "sayNB", types = 0x0000000100003f71 "v16@0:8", imp = 0x0000000100003b50 (KCObjcBuild`-[JSPerson sayNB])}
3. bits-> safe_ro () 拿到 class_ro_t* ro .
1. 成员变量
1. (*ro).ivars 返回 ivar_list_t *ivarList;
2. (*ivarList).get(0) 返回 ivar_t
ivar_t : {offset = 0x00000001000084d8, name = 0x0000000100003f18 "nickName", type = 0x0000000100003f79 "@\"NSString\"", alignment_raw = 3, size = 8}
4. 元类对象地址 *p;那么元类对象的地址就是 *p + 0x20 = bits.
1. bits->data () 拿到 class_rw_t* rw .
1. 类方法
1. (*rw).methods() 返回 method_array_t methodsArray;
2. methodsArray.list 返回 method_list_t_authed_ptr<method_list_t>) authedPtr;
3. authedPtr.ptr 返回 method_list_t* list;
4. list.get(0) 返回method_t类型
5. method_t.big() 查看结构体
method_t::big: { name = "sayNB", types = 0x0000000100003f71 "v16@0:8", imp = 0x0000000100003b50 (KCObjcBuild`-[JSPerson sayNB])}
class_rw_t
- 在运行时生成, 它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。
- 存储属性、方法、协议等信息
- 可读可写
class_ro_t
- 存储了当前类在编译期就已经确定的属性、方法和遵循的协议,里面没有category的方法
- 存储了成员变量、baseMethodList、baseProtocols、baseProperties等信息
- 只读
总结
- 实例方法, 成员变量存储在类中.
- 类方法, 存储在元类中.
category
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
struct property_list_t *_classProperties;
};
- 可以添加实例方法,类方法,甚至可以实现协议,添加属性
- 无法添加实例变量
将分类中的方法/属性/合并到类对象中
- 调用 attachCategories
1.获取所有的分类列表的count- 创建三个数组,
method_list_t **mlists; property_list_t **proplists; protocol_list_t **protolists;
- 使用count, 遍历到每个分类, 将分类中的方法, 属性, 协议添加到三个数组中.
- 创建三个数组,
- 调用 attachLists
- 将原本 rw的 rw->methods, rw->properties, rw->protocols 后移 count 个字节.
- 将mlists, proplists, protolists 中的方法, 属性, 协议添加到rw->methods, rw->properties, ->protocols 前.
结论
所以, 如果分类实现了主类的方法, 会调用分类的方法, 而不调用主类的方法, 因为分类的方法在前边, 方法查找的时候, 先找到了分类的方法就直接执行了, 不会再继续查找了. 而如果多个分类同时实现了同名方法, 就会查找到后编译的分类的方法.
将 父类, 父类分类, 子类, 子类分类 同时实现了+load方法
- 先调用类的+load方法,然后调用子类的+load方法, 然后调用父类分类和子类分类的+load方法;(分类的+load方法, 按照先后顺序, 先编译的先调用, 后编译的后调用);
- 如果子类没有实现load, 但是子类的分类实现了load方法, 那么就会先调用父类的load方法, 然后按照子类的分类和父类的分类的编译顺序, 调用load方法.
- 子类和子类的分类不实现load方法, 然后用子类主动调用load方法 [Son load], 首先会走流程2, 即 父类,的load方法, 父类的分类(先编译)的load方法, 父类的分类(后编译)的load方法, 然后走msg_send()流程调用 父类的分类(后编译)的load方法
将 父类, 父类分类, 子 类, 子类分类同时实现了+initialize方法
+initialize调用时机 : 当一个类在查找方法的时候, 会先判断当前类是否初始化, 如果没有初始化就会去掉用initialize方法, 如果这个类的父类没有初始化, 就会先调用父类的initialize方法, 再调用自己的initialize方法,类在调用initialize时, 使用的是objc_msgSend消息机制调用.
alloc -> class_getInstanceMethod -> lookUpImpOrForward -> _class_initialize(不断向父类递归) -> callInitialize -> objc_msgSend(cls, SEL_initialize);
- 如果父类调用 +initialize, 如果父类的+initialize方法同时被主类和分类实现, 那么就只会调用父类分类的+initialize ,如果多个父类分类同时实现, 那么调用后编译的分类的 +initialize的实现;
- 如果子类调用 +initialize
- 子类/子类分类实现了 +initialize
- 调用父类 +initialize, 如果父类的+initialize方法同时被主类和分类实现, 那么就只会调用父类分类的+initialize ,如果多个父类分类同时实现, 那么调用后编译的分类的 +initialize的实现;
- 用子类 +initialize, 如果子类的+initialize方法同时被主类和分类实现, 那么就只会调用子类分类的+initialize ,如果多个子类分类同时实现, 那么调用后编译的分类的 +initialize的实现;
- 类/子类分类都没有实现了 +initialize
第一次是因为父类没有初始化, 所以会调用父类的+initialize , 第二次调用父类的+initialize是因为调用子类的+initialize方法, 但是子类没有实现, 所以调用了父类的 +initialize;- 就会调用两次父类的 +initialize, 如果父类的+initialize方法同时被主类和分类实现, 那么就会调用两次父类分类的+initialize ,如果多个父类分类同时实现, 那么调用两次后编译的分类的 +initialize的实现;
- 子类/子类分类实现了 +initialize
extension
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
- 编译时决议(在编译的时候就将扩展的所有数据都合并到类中去了)
- 只能声明, 不能实现
- 不能为系统类添加扩展.
关联属性
- _object_set_associative_reference 用来添加关联对象
- objc_destructInstance 中调用 _object_remove_assocations 移除关联对象.