WWDC关于runtime的优化
数据结构的变化(Class Data Structures Changes)
在磁盘上,app二进制文件中的类:包含了最常被访问的信息,指向元类
、父类
和方法缓存的指针
类的数据结构
Clean Memory
是指加载后不会发生更改的内存,class_ro_t
是属于Clean Memory
的。
-
class_ro_t
(只读):一个指向更多数据的指针,存储额外信息的结构 -
class_ro_t
,ro
代表只读,存放了方法、协议、实例变量的信息:
class_ro_t
Dirty Memory
是指在进程运行时会发生更改的内存,类的结构一经使用就会变成Dirty Memory
,因为运行时会向它写入数据,这里指的是class_rw_t
。
-
Dirty Memory
是这个类被分成两部分的原因,可以保持类加载后不会发生更改的数据越多越好,通过分离永远不会更改的数据,可以把大量的类数据存储为Clean Memory
-
class_rw_t
(读写):Methods、Properties、Protocols
,当category
被加载时,它可以向类中添加新方法,可以根据Method Swizzling
方式修改,因为class_ro_t
是只读,所以要把这些放在Class_rw_t
中。
-
First Subclass、Next Subling Class
:包含了运行时才会生成的信息First Subclass、Next Subling Class,所有的类都会变成一个树状结构,就是通过First Subclass和Next Subling Class指针实现的,它允许运行时遍历当前使用的所有类 -
Methods、Properties、protocols
:包含这3个是因为它们可以在运行时进行修改,当category被加载时,它可以向类中添加新的方法,也可以通过runtime API添加它们 -
Demangled Name
:这个是只有Swift才会使用的字段,因为整个数据结构OC与Swift是共享的,但是Swift类本身并不需要这个字段,是为了有人要访问Swfit的OC名称的时候使用的,利用率比较低。
class_rw_t
Dirty Memonry与Clean Memory特点比较:
-
Dirty Memonry
比Clean Memory
要昂贵的多,只要进程在运行,它就必须一直存在 -
Clean Memory
可以进行移除,从而节省更多的内存空间,因为如果需要Clean Memory可以从磁盘中重新加载
Dirty Memory拆分优化原理
Dirty Memonry
即类第一次加载就会存在,运行时就会为它分配额外的内存,运行时分配的存储容量是class_rw_t
,用于读取-编写数据,但是Dirty Memory
里面存在很多Clean Memory
,为了更好的空间利用率,拆分就很有必要!
- 拆分出
class_ro_t
,运行加载时不会被修改的内存
- 这时的
class_rw_t
还是太大,因为里面包含了Methods、Properties、Protoclos
,这3个因素只有使用了category
向class中添加方法或使用了Method swizzle
才会触发的特性,90%的类不会被使用,所以把它们拆分出是必要的,Demangled Name
这个Swift使用的字段拆出去也是必要的,毕竟使用率低,那么Dirty memory
内存结构就变成了class_rw_t
和class_rw_ext_t
两部分。
如何缩小class_rw_t的结构大小
拆掉那些平时不用的部分,可以将class_rw_t
减小一半,对于真的用到了被拆分出去的数据时,可以使用extension
来完成这些,添加到类中供其使用(大约90%的类不需要这个扩展)
class_rw_ext_t
通过终端实际验证微信和Safari的class_rw占用的内存
终端命令
//微信
$ heap WeChat | egrep 'class_ro|COUNT'
COUNT BYTES AVG CLASS_NAME TYPE BINARY
693 55440 80.0 Class.data.readonly (class_ro_t) C libobjc.A.dylib
//Safari
$ heap Safari | egrep 'class_ro|COUNT'
COUNT BYTES AVG CLASS_NAME TYPE BINARY
200 16000 80.0 Class.data.readonly (class_ro_t) C libobjc.A.dylib
class_rw_t与class_ro_t的区别
- 当有类使用了
category
的时候,那么此时的类就有了class_rw_t
的结构,如果未使用分类,那么类就是一个单纯的class_ro_t
的结构。 - 类的内存结构中,有
category Class
的时候有class_rw_t
,没有的时候只有class_ro_t(clean memonry)
成员变量和属性变量
打开objc4-818源码
,创建类LGPerson,LGTeacher
内容如下,在LGPerson *p = [[LGPerson alloc] init];
处添加断点,运行工程
<!-- LGPerson.h文件 -->
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
// 方法 - + OC C/C++ 函数
// 元类
- (void)saySomething;
+ (void)sayNB;
@end
NS_ASSUME_NONNULL_END
<!-- LGTeacher.h文件 -- >
#import <Foundation/Foundation.h>
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGTeacher : LGPerson
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end
NS_ASSUME_NONNULL_END
<!-- main.m文件 -- >
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [[LGPerson alloc] init];
NSLog(@"%@",p);
}
return 0;
}
// lldb调试信息
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008408 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100008428
(class_data_bits_t *) $1 = 0x0000000100008428
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100677f00
// 这里的firstSubclass = nil,为什么不是LGTeacher呢?
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000456
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
// 打印LGTeacher.class之后,再次调试$1->data(),下面 firstSubclass = LGTeacher,原因是什么?
(lldb) p LGTeacher.class
(Class) $4 = LGTeacher
(lldb) p $1->data()
(class_rw_t *) $5 = 0x0000000100677f00
(lldb) p *$5
(class_rw_t) $6 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000456
}
}
firstSubclass = LGTeacher
nextSiblingClass = NSUUID
}
疑问?上面第一次打印firstSubclass = nil
,执行完p LGTeacher.class
再次打印firstSubclass = LGTeacher
,原因是什么?这里留下悬念,后面探讨...
上面WWDC视频中提到成员变量
存储在class_ro_t
中,重新运行上面工程进行调试
KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008408 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100008428
(class_data_bits_t *) $1 = 0x0000000100008428
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001010a1770
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000456
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x0000000100008188
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003f4c "LGPerson"
}
}
baseMethodList = 0x00000001000081d0
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008268
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000082d0
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x0000000100008268
// 获取到有3个属性
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x00000001000083a0
name = 0x0000000100003e18 "hobby"
type = 0x0000000100003f55 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x00000001000083a8
name = 0x0000000100003e28 "_age"
type = 0x0000000100003f7c "i"
alignment_raw = 2
size = 4 //int型占4字节
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
offset = 0x00000001000083b0
name = 0x0000000100003e2d "_name"
type = 0x0000000100003f55 "@\"NSString\""
alignment_raw = 3
size = 8 // 字符串内存中占8字节
}
(lldb)
成员变量和属性变量的区别
创建新工程类的属性与变量
,main.m
中创建LGPerson
类
// 成员变量 vs 属性 VS 实例变量
// {}中声明的是成员变量,这里的实例变量objc是一种特殊的成员变量
@interface LGPerson : NSObject {
// STRING int double float char bool
NSString *hobby;
int a;
NSObject *objc; // 实例变量
}
// @property声明的是属性变量
@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;
@end
// 通过clang编译成main.cpp文件查看
$ clang -rewrite-objc main.m -o main.cpp
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nnickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_anickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_aname;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *hobby;
int a;
NSObject *objc;
NSString *_nickName;
NSString *_acnickName;
NSString *_nnickName;
NSString *_anickName;
NSString *_name;
NSString *_aname;
};
// @property (nonatomic, copy) NSString *nickName;
// @property (atomic, copy) NSString *acnickName;
// @property (nonatomic) NSString *nnickName;
// @property (atomic) NSString *anickName;
// @property (nonatomic, strong) NSString *name;
// @property (atomic, strong) NSString *aname;
/* @end */
// @implementation LGPerson
// self + OBJC_IVAR_$_LGPerson$_nickName 通过内存平移获取
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }
static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }
static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }
// @end
......
结论
-
成员变量
是生命在类的{}
中的 -
属性变量
是用@property
方式声明的 -
属性变量
在底层编译阶段会变成_方式
的成员变量
-
属性变量
会自动生成getter
和setter
方法
疑问?上面的set方法为什么会有objc_setProperty
和内存平移赋值
两种方式?下面进行探讨...
TypeEncoding
返回实例变量的类型字符串。Apple Documents地址
// 这里的 "c" "i" "s" "q" 都表示编码
void lgTypes(void){
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d7_5qn4fnqn0p197t4lkw1bw1p40000gn_T_main_418517_mi_0,"c");
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d7_5qn4fnqn0p197t4lkw1bw1p40000gn_T_main_418517_mi_1,"i");
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d7_5qn4fnqn0p197t4lkw1bw1p40000gn_T_main_418517_mi_2,"s");
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d7_5qn4fnqn0p197t4lkw1bw1p40000gn_T_main_418517_mi_3,"q");
......
// 这里的"@\"NSString\""表示什么?
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[9];
} _OBJC_$_INSTANCE_VARIABLES_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
9,
{{(unsigned long int *)&OBJC_IVAR_$_LGPerson$hobby, "hobby", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_LGPerson$a, "a", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_LGPerson$objc, "objc", "@\"NSObject\"", 3, 8},
......
// "@16@0:8"编码又表示什么?
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[24];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
24,
{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
......
上面这些全是编码,字符含义可以在Apple Documents官网
查到
这里举例@16@0:8
的含义
-
@
:表示id类型 -
16
:表示占用的内存 -
@
:表示id -> self -
0
:表示从0号位置开始 -
:
:表示SEL -
8
:表示从8号位置开始
再来举例v24@0:8@16
含义
setter方法的底层原理
探讨上面set方法为什么会有objc_setProperty
和内存平移赋值
两种方式?为什么copy
修饰的属性使用了objc_setProperty
,而strong
修饰的没有?
- 在
LLVM源码
中搜索objc_setProperty
,找到如下所示的getOptimizedSetPropertyFn
方法
从这里可看出,针对不同修饰符
,返回是不同的
- 上述的几个name分别对应
objc4-818.2
源码中的如下方法
- 通过汇编调试发现,最终都会走到
objc_storeStrong
- copy修饰的属性汇编调试结果发现执行到
objc_storeStrong
- strong修饰的属性汇编调试结果执行到
objc_storeStrong
- 源码中搜索
objc_storeStrong
,有如下源码,主要也是retain新值,release旧值
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);//retain新值
*location = obj;
objc_release(prev);//release旧值
}
- llvm编译源码中搜索
objc_storeStrong
,找到EmitARCStoreStrongCall
方法,发现copy 和 strong修饰的属性执行的策略是不一致的 - llvm中搜索
EmitARCStoreStrongCall
方法,在GenerateCopyHelperFunction
方法有调用,然后在这里发现了strong
和weak
的不同处理
- 如果是weak修饰,执行
EmitARCCopyWeak
方法,weak
在底层的调用是objc_initWeak
- 如果是strong修饰,执行
EmitARCStoreStrongCall
方法
得出结论
- copy和strong修饰的属性在底层编译的不一致,主要还是llvm中对其进行了不同的处理的结果。copy的赋值是通过
objc_setProperty
,而strong的赋值时通过self + 内存平移
(即将指针通过平移移至name所在的位置,然后赋值),然后还原成strong类型 - strong & copy 在底层调用
objc_storeStrong
,本质是新值retain,旧值release - weak 在底层调用
objc_initWeak
类方法存储的api方式
上面我们探索类方法存储的时候,使用的是方式是源码+lldb调试
,还有一种方式是通过系统api来获取,代码如下。lgInstanceMethod_classToMetaclass(pClass);
添加断点进行调试
// 用于获取类的方法列表
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
// 用于获取类的实例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
// 用于获取类的类方法
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
// 用于获取方法的实现
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
// sel -> imp 方法的查找流程 imp_farw
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// LGTeacher *teacher = [LGTeacher alloc];
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
NSLog(@"*************");
lgObjc_copyMethodList(metaClass);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
// 打印信息
// 类中打印的方法
Method, name: sayHello
Method, name: name
Method, name: .cxx_destruct // 添加的cxx析构方法
Method, name: setName:
Method, name: obj
Method, name: setObj:
2021-07-18 23:03:44.702431+0800 002-类方法归属分析[38880:4168691] *************
// 元类中打印的方法
Method, name: sayHappy
lgInstanceMethod_classToMetaclass - 0x1000081c0-0x0-0x0-0x100008158
lgClassMethod_classToMetaclass-0x0-0x0-0x100008158-0x100008158
2021-07-18 23:23:14.862891+0800 002-类方法归属分析[38880:4168691] 0x100003ac0-0x7fff201faac0-0x7fff201faac0-0x100003b00
2021-07-18 23:23:14.863444+0800 002-类方法归属分析[38880:4168691] lgIMP_classToMetaclass
得出结论
-
底层没有获取类方法的方法
,获取类方法的底层还是获取实例方法
,只是获取的是元类的实例方法
。 -
class_getInstanceMethod
:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL -
class_getClassMethod
:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。 -
class_getMethodImplementation
:获取方法的具体实现,如果未查找到,则进行消息转发
这也论证了元类中为什么会有类对象的类方法