OC 类原理探索 系列文章
- OC 类原理探索:类的结构分析
- OC 类原理探索:类结构分析补充
- OC 类原理探索:属性的底层原理
前言
上篇OC 类原理探索:类的结构分析对类结构做了整体的分析,今天做一些补充和拓展。
一、WWDC 2020 类结构的优化
WWDC 2020 – runtime 优化 中介绍了类数据结构的优化、方法列表的优化和tagged pointer格式的优化3方面内容,我们今天只分析类数据结构的优化。
1. clean memory 和 dirty memory 的区别
clean memory
clean memory是指加载后不会发生更改的内存;class_ro_t就属于clean memory因为它是只读的;clean memory可以进行移除从而节省更多的内存空间,因为如果你需要clean memory,系统可以从磁盘中重新加载。
dirty memory
dirty memory是指在进程运行时会发生更改的内存;- 类结构一经使用就会变成
dirty memory,因为运行时会向它写入新的数据,例如创建一个新的方法缓存并从类中指向它; dirty memory比clean memory要昂贵的多,只要进程在运行,他就必须一直存在;macOS可以选择换出dirty memory,但因为iOS不使用swap所以dirty memory在iOS中代价很大。
dirty memory是这类数据被分成两部分的原因,可以保持清洁的数据越多越好,通过分离出那些永远不会更改的数据,可以把大部分的类数据存储为clean memory。
2. class_rw_t 的优化
当类第一次被加载到内存时的结构:

虽然这些数据足以让我们开始,但运行时需要追踪每个类的更多信息。
所以当一个类首次被使用,运行时会为它分配额外的存储容量,这个运行时分配的存储容量是class_rw_t用于读取-编写数据,看下面的结构:

在这个数据结构中,我们存储了只有在运行时才会生成的新信息。例如,所有的类都会连接成一个树状结构,这是通过使用First Subclass和Next Sibling Class指针实现的,这允许运行时遍历当前使用的所有类,这对于使方法缓存无效非常有用。
但为什么方法和属性在只读数据中有,这里还有方法和属性呢?因为它们可以在运行时进行更改,当categry被加载时它可以向类中添加新的方法,也可以使用运行时API动态的添加它们,因为class_ro_t是只读的,所以需要在class_rw_t中追踪这些东西。
这样做的结果是会占用相当多的内存,在任何给定的设备中都有许多类在使用,在iPhone上的整个系统中,大约30兆字节这些class_rw_t结构,那么我们如何缩小这些结构呢?我们在读取-编写部分需要这些东西,因为它们可以在运行时进行更改,但是通过检查实际设备上的使用情况,发现大约只有10% 的类真正的更改了它们的方法。
而且只有Swift类会使用这个Demangled name字段,并且Swift类并不需要这一字段,除非有东西询问他们的Objective-C名称时才需要。

所以 我们可以拆掉那些平时不用的部分,这将class_rw_t的大小减少了一半,
将需要动态更新的部分提取出来,存入class_rw_ext_t。
大约90%的类从来不需要这些扩展数据,这在系统范围内可节省大约14MB的内存,这些内存可以用于更有效的用途,比如存储你的app的数据。
二、API 方式解析类方法的存储
OC 类原理探索:类的结构分析 中用lldb对类方法的存储进行了解析,今天用API的方式来进行解析。
定义SSLPerson类:
@interface SSLPerson : NSObject {
NSString *_hoby;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
- (void)eat;
+ (void)run;
@end
复制代码
1. 获取方法列表:class_copyMethodList
void logObjc_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);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
Class pClass = object_getClass(person);
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
logObjc_copyMethodList(pClass);
SSLog(@"*************");
logObjc_copyMethodList(metaClass);
}
return 0;
}
打印结果:
Method, name: eat
Method, name: name
Method, name: .cxx_destruct
Method, name: setName:
Method, name: age
Method, name: setAge:
*************
Method, name: run
复制代码
- 分别打印了
类和元类中的所有方法; metaClass打印的run属于元类中方法,其他属于类中方法。
2. 获取类方法:class_getInstanceMethod
void logInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(eat));
Method method2 = class_getInstanceMethod(metaClass, @selector(eat));
Method method3 = class_getInstanceMethod(pClass, @selector(run));
Method method4 = class_getInstanceMethod(metaClass, @selector(run));
NSLog(@" %p \n %p \n %p \n %p",method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
Class pClass = object_getClass(person);
logInstanceMethod_classToMetaclass(pClass);
}
return 0;
}
打印结果:
0x1000083b8
0x0
0x0
0x100008350
复制代码
class_getInstanceMethod(pClass, @selector(eat))得到了地址0x1000083b8,class_getInstanceMethod(metaClass, @selector(run))得到了地址0x100008350。- 说明了
eat属于类中方法,run属于元类中方法。
3. 获取元类方法:class_getClassMethod
void logClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(eat));
Method method2 = class_getClassMethod(metaClass, @selector(eat));
Method method3 = class_getClassMethod(pClass, @selector(run));
Method method4 = class_getClassMethod(metaClass, @selector(run));
SSLog(@" %p \n %p \n %p \n %p",method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
Class pClass = object_getClass(person);
logClassMethod_classToMetaclass(pClass);
}
return 0;
}
打印结果:
0x0
0x0
0x100008350
0x100008350
复制代码
class_getClassMethod函数解析:如果是个普通类就获取它的元类方法,如果已经是元类了,直接获取方法;- 所以
class_getClassMethod(pClass, @selector(run))和class_getClassMethod(metaClass, @selector(run))得到了方法地址0x100008350。
4. 获取类 imp :class_getMethodImplementation
void logIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(eat));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(eat));// 0
// sel -> imp 方法的查找流程 imp_farw
IMP imp3 = class_getMethodImplementation(pClass, @selector(run)); // 0
IMP imp4 = class_getMethodImplementation(metaClass, @selector(run));
SSLog(@" %p \n %p \n %p \n %p",imp1,imp2,imp3,imp4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
Class pClass = object_getClass(person);
logIMP_classToMetaclass(pClass);
}
return 0;
}
打印结果:
0x100003ba0
0x7fff203895c0
0x7fff203895c0
0x100003bb0
复制代码
imp1是pClass的类方法,imp4是metaClass的元类方法。- 为什么
imp2和imp3也有值呢,看源码分析一下。
__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
复制代码
可以看到!imp时,会返回_objc_msgForward,所以imp2和imp3有值。
三、isKindOfClass 面试题
看下面代码及打印结果:
void logKindOfDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[SSLPerson class] isKindOfClass:[SSLPerson class]];
BOOL re4 = [(id)[SSLPerson class] isMemberOfClass:[SSLPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[SSLPerson alloc] isKindOfClass:[SSLPerson class]];
BOOL re8 = [(id)[SSLPerson alloc] isMemberOfClass:[SSLPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
logKindOfDemo();
}
return 0;
}
打印结果:
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
复制代码
为什么会有这样的结果呢,接下来我们用源码进行方法分析。
1. 错误的源码
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
复制代码
网上针对这段源码分析的文章很多,其实这是不对的,这段代码是老版的,OC 2.0已经不用这段代码了。
2. 源码分析
OC 对象原理探索(一)中介绍过底层探索的三种方式,我们今天用汇编跟源码的方式解析这道面试题。
打断点 -> 开启汇编调试 -> 运行项目 -> 汇编中找到底层方法objc_opt_isKindOfClass:

isKindOfClass 源码分析
打开 objc4-818.2 源码,搜索objc_opt_isKindOfClass方法:
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
复制代码
objc_opt_isKindOfClass方法解析:对象和类都调用了objc_opt_isKindOfClass方法;obj先获取它的类或者元类赋值给tcls,与otherClass进行比较,如果相等直接返回YES,如果不相等tcls循环取其父类与otherClass进行比较。
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];解析:[NSObject class]进入方法获取它的元类赋值给tcls,tcls = NSObject元类与[NSObject class]进行比较显然不相等;- 进入循环
tcls = NSObject元类取其父类是NSObject类,tcls = NSObject类与[NSObject class]进行比较相等,所以re1 = 1;
BOOL re3 = [(id)[SSLPerson class] isKindOfClass:[SSLPerson class]];解析:[SSLPerson class]进入方法获取它的元类赋值给tcls,tcls = SSLPerson元类与[SSLPerson class]进行比较显然不相等;- 进入第一次循环
tcls = SSLPerson元类取其父类是根元类,tcls = 根元类与[SSLPerson class]进行比较还是不相等; - 第二次循环
tcls = 根元类取其父类是NSObject类,tcls = NSObject类与[SSLPerson class]进行比较还是不相等; - 第三次循环
tcls = NSObject类取其父类是nil,tcls = nil与[SSLPerson class]进行比较还是不相等,所以re3 = 0。
re5 = 1。re7 = 1。
isMemberOfClass 源码分析
打开 objc4-818.2 源码,搜索isMemberOfClass方法:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
复制代码
+ (BOOL)isMemberOfClass方法解析:- 当前类的元类和
cls进行比较;
- 当前类的元类和
- (BOOL)isMemberOfClass方法解析:- 当前对象的类和
cls进行比较;
- 当前对象的类和
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];解析:[NSObject class]的元类和[NSObject class]进行比较不相等,所以re2 = 0。
BOOL re4 = [(id)[SSLPerson class] isMemberOfClass:[SSLPerson class]];解析:[SSLPerson class]的元类和[SSLPerson class]进行比较不相等,所以re4 = 0。
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];解析:[NSObject alloc]的类和[NSObject class]进行比较相等,所以re6 = 1。
BOOL re8 = [(id)[SSLPerson alloc] isMemberOfClass:[SSLPerson class]];解析:-
[SSLPerson alloc]的类和[SSLPerson class]进行比较相等,所以re8 = 1。
-






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)