WWDC关于runtime的优化
Clean Memory 和 Dirty Memory
在iOS里,内存分为两种:
clean Memory 指的是加载后不会发生变化的内存
dirty Memory 指的是进程运行时会发生改变的内存
dirty Memory比clean Memory更昂贵,因为前者一经使用就必须一直存在。
clean Memory可以进行移除,从而节省更多内存空间。因为如果你需要 clean memory ,系统可以从磁盘中重新加载。
macOS可以选择 swap dirty memory,但因为iOS 不使用swap,所以 dirty memory 在iOS中代价很大。dirty memory 是这个类数据被分成两部分的原因。可以保持 clean memory的数据越多越好。通过分离出那些永远不会更改的数据,可以把大部分的类数据存储为 clean memory.
因为clean Memory和dirty Memory的存在,所以类的数据被分成两部分:
一经编译,就不可修改的数据。(ro)
运行过程中可修改的数据。(rw)
ro:数据是只读的,所以它属于clean Memory。
(它是从沙盒读取,ro的数据在编译的时候就已经确定了。)
rw:数据是可读可写的,所以它属于dirty Memory。
(rw的数据存放的是运行时动态修改的数据)
rwe:将ro里面的数据拷贝一份,并且新增的数据也加到rwe里面. 前面我们提到了rw存在的那片内存属于dirty Memory,而dirty Memory在iOS里的代价很大,我们应该尽可能减少rw里的数据存在。
扩展class_rw_t、class_rw_ext_t、class_ro_t
class_rw_ext_t出现前后的class_rw_t对比
将需要动态修改的内容拆分出来,那么 class_rw_t 的大小就变为之前的一半。
类的结构
三者之间的关系
rw里面并不存储属性、协议、方法等变量,只提供了获取这些变量的方法。 假如有的类在运行时改变属性、方法、协议,那么就会去ro里面读取相应的数据,否则就去rwe里面读取数据(rwe会拷贝一份ro的变量数据,而且还包含扩展的变量数据)。
setter方法的底层原理
Person *p = [[Person alloc] init];
p.name = @"jim";
上面的p.name
实际上调用了setName
进行赋值,而setName
在llvm
期间,变成set_property
方法的调用。之所以会去查看llvm
的源码,而不是objc
的源码,是因为objc
底层是看不到setName
和set_property
的关联的。
通过Clang来探索一下属性的cpp代码
源码:
@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;
cpp代码
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; }
通过clang可以得出以下结论:
getter
方法底层是通过内存平移
获取值;
setter
方法底层是通过内存平移
和set_property
方法实现,而当属性用copy
修饰的时候才会走set_property
方法,否则通过内存平移
赋值。
@16@0:8 是类型编码,方法的签名时候使用。
查看类型编码的官网地址:类型编码
类方法的存储
在LGPerson
中定义一个类方法+(void)sayNB;
lldb调试
查找它存在的位置:
API方式解析
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);
}
return 0;
}
打印如下:
Method, name: sayHello
Method, name: name
Method, name: .cxx_destruct
Method, name: setName:
Method, name: obj
Method, name: setObj:
*************
Method, name: sayHappy
lgInstanceMethod_classToMetaclass - 0x1000081b8-0x0-0x0-0x100008150
lgClassMethod_classToMetaclass-0x0-0x0-0x100008150-0x100008150
[74118:14706714] 0x100003b30-0x7ff800ee11c0-0x7ff800ee11c0-0x100003b70
[74118:14706714] lgIMP_classToMetaclass
得出以下结论:
在object底层没有类方法,只有对象方法。
对象方法存储在类里面;
类方法存储在元类当中,在元类中表现为对象方法;
引申 - 元类
为什么要有元类?
是为了复用消息机制,objc_msgsend
,判断是消息接受者是类对象还是实例对象,方法是类方法还是实例方法,减少objc_msgsend
的复杂度。