类的结构
WWDC2020视频地址-> WWDC类的结构
cleanMemory:加载之后不会变化的内存,
class_ro_t就是,因为它是只读的
dirtyMemory:运行时会发生改变的内存,类结构一经使用就变成了dirtyMemory,以为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它。dirtyMemory在iOS中成本很高 ,dirtyMemory是这个类数据被分成两部分的原因,
class_ro_t:类加载过程中并不会改变的信息
class_rw_t存储了只有在运行时才会生成的新信息,同时我们发现大约只有10%的类去真正改变了它们,全量追踪的话内存开销比较大,所以可以拆掉最大可能会变动的部分做成class_rw_ext_t,demangled nameswift并不会使用,而方法属性协议大部分类并不会去动态添加。
class_rw_ext_t:category被加载时可以向类中添加新的方法
成员变量和属性(class_rw_t/class_ro_t)
打开源码,我们在上一篇的Person类中新增一个ivar,注意新增方法的不同
上一篇我们打印了当前Person类的内存情况,发现这样添加的subject并不在当前的property_list中,那么这个在哪里呢?我们在源码中定位到
struct class_rw_t中发现里面有一个获取ro的方法class_ro_t *ro(),这里面是类在运行中不可改变的内容也就是上面说的干净内存,我们来验证下,看是否在这里面
我们找到了一个ivars,打印输出
果然,subject在
class_ro_t的ivars里面,由此我们可以得出一个结论,由property生成的是属性,subject这种是成员变量,属性和成员变量的存储地方不同。成员变量存储在ro里,属性存储在rw里面的property_list中,并且属性会自动在ro里面生成带下划线_的成员变量。
或者我们生成.cpp文件验证下,新建一个工程,在main中创建一个Person,果然在IMPL里面会自动生成一个_name的成员变量
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *hobby;
NSString *_name;
};
在这个下面就是他们的set和get方法
static void _I_Person_setNickName_(Person * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _nickName), (id)nickName, 0, 1); }
static void _I_Person_setNnickName_(Person * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_nnickName)) = nnickName; }
经过仔细对比,我们猜测,使用copy修饰的属性set方法使用的是objc_setProperty,而非copy修饰的属性set方法使用的是内存平移(char *)self + OBJC_IVAR_$_Person$_nnickName
当然啦,runtime也直接提供了获取属性名和实例变量名的方法:
void lgObjc_copyIvar_copyProperies(Class pClass){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
NSString *ivarName = [NSString stringWithUTF8String: ivar_getName(ivar)];
NSLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
NSLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
类方法和对象方法
我们在类中添加实例方法和类方法,这两种方法是可以同名的,但是OC的底层是c/c++是没有类方法和实例方法的,统一成为函数,如果我们的类方法和实例方法都放到类里面,那么两个方法是无法区分的。所以这里出现了元类。我们继续验证下
步骤同上篇一样这里不赘述,最终拿到了Person的类方法,验证了类方法存在于元类中。
同样的runtime也直接提供了获取类方法和对象方法的函数
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));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
或者可以查询在当前的类/元类中是否有这个方法,下面就从各方面辩证输出打印
void lgInstanceMethod_classToMetaclass(Class pClass){
// 实例方法、对象方法
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); //1
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); // 0
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy)); // 0
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy)); //1
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)); //0
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));//0
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));//1
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));// 1
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
经常面试的时候会问到,为什么元类中有sayHappy这个类方法?元类中有sayHappy这个对象方法毋庸置疑,但是为什么也会有这个类方法呢,我们看下底层的源码
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
得到:元类的类方法也就等于元类的实例方法。也就是说在底层来说,是没有类方法这个概念的,只有对象方法
IMP
我们从方法的实现也来验证下
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
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));
// 0x100003ac0-0x7fff201f25c0-0x7fff201f25c0-0x100003b00
NSLog(@"%s-%p-%p-%p-%p",__func__,imp1,imp2,imp3,imp4);
}
1,4存在是没有疑问的,既然类方法存在于元类中,那么为什么在元类中会有sayHello的imp,Person中会有sayHappy的imp呢?查看源码sel查找imp的过程,我们发现如果没有imp回返回一个_objc_msgForward
__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
...
if (!imp) {
return _objc_msgForward;
}
return imp;
}
补充扩展
在.cpp文件中,我们能看到这些编码,他们代表的分别是什么呢?runtime中提供了一个方法ivar_getTypeEncoding command + shift + 0搜索document
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_Person_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_Person_setNickName_},
{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_Person_acnickName},
"@16@0:8"
@-> 返回类型 id,上面是个nickName的get方法,有返回值,所以为@
16-> 参数所占用的内存
@-> 隐藏参数id get方法中有一个隐藏参数id self
0-> 从0号位置开始
:-> sel
8-> 从8号位置开始
objc_setProperty
查看LLVM源码,定位到propertyImplStrategy,因为这个set方法需要在编译时就完成,所以需要查看LLVM
if (IsCopy) {
Kind = GetSetProperty
return;
}