上一节我们分析了类的结构,成员变量、属性、实例方法和类方法的读取。
今天我深入拓展一下:
- 怎么知道成员变量、属性存放位置的?
-
v24@0:8@16这种符号啥意思?(熟悉编码表)
-
- 类的归属
1. 怎么知道成员变量、属性存放位置的?
当我们只知道objc_class结构的时候,无法确定某类属性或方法存放在哪里时:
- 可以给一个特殊的名称
- 通过clang将文件静态编译成cpp文件
- 搜索特殊名称。查看系统存放位置。
例如: 我们不知道
成员变量存放在哪。
- 定义一个
hobby成员变量@interface HTPerson : NSObject { NSString * hobby; }
在
main.m中实例化HTPerson对象
clang -rewrite-objc main.m -o main.cpp编译成cpp静态文件。打开
main.cpp文件,搜索hobby
image.png
image.png
image.png因为是
特殊名称(系统中基本不会出现的名称),所以一下就可以搜索出来。上面就是这个名称出现的所有位置。 我们对比
objc_class的data()数据格式来看:
image.png发现
class_ro_t类型的只有ro()函数。
- 进入
class_ro_t查看格式。
image.png顺利找到
ivar_list_t。定位了成员变量位置。
实践过程可查看上一节
这里介绍的是通用定位方法。 不局限于成员变量
2. v24@0:8@16这种符号啥意思?
我们查看cpp文件时,发现函数都有v24@0:8@16这样的符号。

SEL 和 IMP
每个方法都有SEL和IMP。
- SEL: 方法编号
- IMP: 函数指针地址
- SEL和IMP: 组成
键值对。
我们调用函数,是调用OC上层封装好的函数。 通过SEL方法编号 -> 找到对应的IMP指针地址 -> 返回指针指向的底层实现。
- 打开
Xcode开发者文档
开发者文档.png
搜索ivar_getTypeEncoding

点击进入官方文档,查看所有类型编码 👉 快捷通道

这些就是各类型简写。 以后看到这些就清晰明朗了。😃
v24@0:8@16:
v: void ,@: 一个对象 ,:SEL 函数选择器描述:定义一个
返回值是void,总内存大小为24字节的函数,第一个入参是对象,从0字节开始,第二个入参SEL,从8字节开始,第三个入参是对象,从16字节开始对比下面实际函数看就一目了然了:
- static void I_HTPerson_setName(HTPerson *
self, SEL_cmd, NSString *name)
第一个入参HTPerson实例对象, 第二个入参SEL方法编号, 第三个入参name字符串
类似属性值也可以参看相关文档了解
{"name","T@\"NSString\",&,N,V_name"
文档中搜索property_getAttributes, 找到👉 官方描述
3. 类的归属
上一节在结束时,我们验证了
对象的方法存在类中,类的方法存在元类中
面试题一: 类的归属
准备测试数据
@interface HTPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation HTPerson
- (void)sayHello{
NSLog(@"HTPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"HTPerson say : Happy!!!");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson *person = [HTPerson alloc];
Class pClass = object_getClass(person);
// 这里加测试函数
// Objc_copyMethodList(pClass); // 问题1
// InstanceMethod_classToMetaclass(pClass); // 问题2
// ClassMethod_classToMetaclass(pClass); // 问题3
// IMP_classToMetaclass(pClass); // 问题4
}
return 0;
}
- 问题1:Objc_copyMethodList打印的函数有哪些?
void Objc_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);
}
- 这里需要弄清楚
class_copyMethodList的原理。
从
月月这位优秀童鞋那学来的学习方法,分享下:
- 不懂的
先查官方文档,清楚功能。再去源码究其根源。
官方解释:
打开帮助文档:
官方文档搜索
class_copyMethodList:class_copyMethodList大致意思是说:
- 如果这个类有
实现了的实例函数,就返回一个包含所有实例函数的数组。最后你必须free释放这个数组。- 如果
当前类没有实例函数,或者当前类为空,返回Null.并且outCount为0
进入objc4源码中,进入
class_copyMethodList方法。
image.png我们可以看到完整的流程。 从
HTPerson类中读取data()的methods()函数。并将其另辟空间result存储后,返回result。
这个result就是类的
Methods函数数组。学完上一节我们知道,类的Methods中存储的是所有实例方法,类方法是存储在元类中的。看完源码,你应该要知道代码
最后为何要加上free了吧。 因为result是新开辟的空间,需要手动释放。
HTPerson只有
sayHello一个实例方法。所以class_copyMethodList打印的只有是sayHello。- image.png
- 问题2:InstanceMethod_classToMetaclass中哪些函数不是0x0?
void InstanceMethod_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));
NSLog(@"%s:", __func__);
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
}
- 这个需要弄清楚
class_getInstanceMethod的原理
官方解释:
image.png大致意思是说:
- 如果传入的
类或它的父类不包含这个实例方法,就返回Null
源码分析:
image.png进入
lookUpImpOrForward。 寻找impimage.png
- 进入
_class_getMethod_class_getMethod
- 进入
getMethod_nolockimage.png
通过熟悉源码和文档,我们可以知道:
-
lookUpImpOrForward,就是使用一切办法(本类、父类、消息转发等)将Imp找到。 -
class_getInstanceMethod, 因为上面lookUpImpOrForward已经将imp找到了。所以class_getInstanceMethod就只需要走类继承(superclass)这条线来常规读取imp了。先在自己本类找,找不到再到superclass中寻找。如果运行到根类, 还找不到就条件终止(根类的父类为nil),返回nil。
通过上一节学习,我们知道,实例方法存在类中,而类方法是存储在元类中的。
这里
sayHello是实例方法,sayHappy是类方法。 所以打印结果中,第一个和第四个有值,第二个和第三个为空。- image.png
- 问题3:ClassMethod_classToMetaclass中哪些函数不是0x0?
void ClassMethod_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));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s", __func__);
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
}
- 这个需要弄清楚
class_getClassMethod的原理
官方解释:
image.png大致意思是说:
- 如果传入的
类或它的父类不包含这个实例方法,就返回Null
源码分析:
image.png发现这个函数跟问题2进入的一样,都是
class_getInstanceMethod
- 不同的是这里入参是
getMeta, 我们进入查看:image.png发现个
有意思的事情。 我们传入cls
- 代码判断
isMetaClass是否为元类,如果是元类就返回cls本类,如果不是, 我就返回本类的isa。用大白话解释:
兄弟,类方法都放在元类里。如果你给我的是元类,我可以直接操作。 如果不是元类,我就免费帮你转成元类(类的isa指向元类),再去操作。总之,我这只办理元类的业务。
- 在
C和C++的层面不存在类方法和对象方法,一切方法的处理,在它确定对象(类还是元类)之后,都交给class_getInstanceMethod来处理。 所以后续方法就跟问题2的处理一样了。
因为这题是获取类方法。 题中类方法只有sayHappy,所以第一个和第二个都是不存在。 类方法是存在元类中,所以第四个是存在的。
- 那
第三个呢? 第三个上面说了,class_getClassMethod只处理类方法,如果不是元类,就帮你转成元类。 - 所以
第三个实际上是转成了元类,再调用了元类中储存的类方法。
所以打印结果如下:

- 问题4:IMP_classToMetaclass中哪些函数不是0x0?
void class_getMethodImplementation(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));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%s:",__func__);
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
}
- 这个需要弄清楚
class_getMethodImplementation的原理
官方解释:
image.png大致意思是说:
- 如果传入的
类或它的父类都不包含这个实例方法,就使用运行时的消息转发机制。
源码分析:
image.png我们发现,如果我们
找不到方法对应的imp,就使用运行时的消息转发。让能处理这个消息的对象来接收处理。
我们知道:
第一个实例方法和第四个类方法,本身是可找到对应的imp的,所以是存在的。第二个和第三个,我们会调用消息转发机制。
我们打印查看,最终都是存在的。

总结
class_getInstanceMethod: 获取实例方法。
自身类->父类->...->根类->nil这一条线,只要能找到对应实例方法,就返回方法imp;否则,返回null。class_getClassMethod: 获取类方法。
如果传入的不是元类,就转成元类再调用class_getInstanceMethod方法。 总之,类方法只存在元类中。class_getMethodImplementation: 获取方法的实现。
如果未找到,就进行消息转发。
面试题二:isKindOfClass 与 isMemberOfClass
打印小技巧:
#ifdef DEBUG #define HTLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## >__VA_ARGS__] UTF8String]); #else #define HTLog(format, ...); #endif
- 面试题:
@interface HTPerson : NSObject
@end
@implementation HTPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[HTPerson class] isKindOfClass:[HTPerson class]];
BOOL re4 = [(id)[HTPerson class] isMemberOfClass:[HTPerson class]];
HTLog(@" re1 :%hhd re2 :%hhd re3 :%hhd re4 :%hhd",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[HTPerson alloc] isKindOfClass:[HTPerson class]];
BOOL re8 = [(id)[HTPerson alloc] isMemberOfClass:[HTPerson class]];
HTLog(@" re5 :%hhd re6 :%hhd re7 :%hhd re8 :%hhd",re5,re6,re7,re8);
}
return 0;
}
官方文档
image.pngimage.png
源码分析
image.pngisKindOfClass:
类方法: 类的Isa指向的是元类,沿着父类(superclass)这条线寻找,检查是否存在和入参cls相等的类。实例方法: 对象的Isa指向的是本类,沿着父类(superclass)这条线寻找,检查是否存在和入参cls相等的类。isMemberOfClass
类方法: 判断Isa指向的元类,是否与入参cls相等。实例方法: 判断Isa指向的本类是否与入参cls相等。
💣 💥
当我们以为一切仅在掌控之中时,却发现isKindOfClass的类方法和实例方法压根没调用😭
加入
断点
image.png打开
汇编模式image.pngimage.png
- 为什么要调用
objc_opt_class呢?我们在objc源码层找不到调用依据,打开llvm搜索objc_opt_isKindOfClass。image.png我们看到了熟悉的
objc_alloc。你是否记得NSObject的alloc方法也没走objc4源码的alloc类方法? 而是llvm在编译层就将其处理好了。
在这个表中,
objc_opt_isKindOfClass静静地跟着objc_alloc一起躺着,我们看注释,可以知道苹果官方设计的原因,因为这些函数极少被改变,所以为了加速性能,苹果在llvm编译层就已经将其优化处理。 比如isKindOfClass如果没有被外部重写,在被调用时都是直接消息转发执行objc_opt_isKindOfClass。
objc4源码中查看objc_opt_isKindOfClass内部实现:image.png发现内部实现就跟
isKindOfClass的方法一样。
特点:
类方法的初始值是元类,实例方法的初始值是本类
objc_opt_isKindOfClass: 是底层实现,有遍历操作
类方法和实例方法在底层统称为方法。(入参是实例对象,初始值为本类; 入参是类,初始值是元类)
isMemberOfClass:类方法和实例方法都无遍历操作,直接比较
在开始之前,我们请上经典isa指向和superclass继承图:
- isa指向和superclass继承
掌握秘诀,答题开始:
-
re1:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
isKindOfClass类方法,调用objc_opt_isKindOfClass,初始值为元类,拥有遍历操作
- 获取
[NSObject class]中isa指向的类:NSObject元类- 判断
NSObject元类与NSObject类,不相等。- 继续寻找
NSObject元类的superclass:NSObject类- 与
[NSObject class]相等。 返回true
答案: True
-
re2:
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
isMemberOfClass类方法,初始值为元类,无遍历操作,直接比较
- 获取
[NSObject class]中isa指向的类:NSObject元类- 判断
NSObject元类与NSObject类,不相等。 返回false
答案: False
-
re3:
BOOL re3 = [(id)[HTPerson class] isKindOfClass:[HTPerson class]];
-
isKindOfClass类方法,调用objc_opt_isKindOfClass,初始值为元类,拥有遍历操作
- 获取
[HTPerson class]中isa指向的类:HTPerson元类- 判断
HTPerson元类与HTPerson类,不相等。- 继续层层寻找
HTPerson元类的superclass- 依次找到:
NSObject元类、NSObject类、nil。都与HTPerson类不相等,返回false
答案: False
-
re4:
BOOL re4 = [(id)[HTPerson class] isMemberOfClass:[HTPerson class]];
isMemberOfClass类方法,初始值为元类,无遍历操作,直接比较
- 获取
[HTPerson class]中isa指向的类:HTPerson元类- 判断
HTPerson元类与HTPerson类,不相等。返回false
答案: False
-
re5:
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
isKindOfClass对象方法,调用objc_opt_isKindOfClass,初始值为本类,拥有遍历操作
- 获取
[NSObject alloc]的本类:NSObject类- 判断
NSObject类与[NSObject class]相等, 返回True
答案: True
-
re6:
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
isMemberOfClass对象方法,初始值为本类,无遍历操作,直接比较
- 获取
[NSObject alloc]的本类:NSObject类- 判断
NSObject类与[NSObject class]相等, 返回True
答案: True
-
re7:
BOOL re7 = [(id)[HTPerson alloc] isKindOfClass:[HTPerson class]];
isKindOfClass对象方法,调用objc_opt_isKindOfClass,初始值为本类,有遍历操作
- 获取
[HTPerson alloc]的本类:HTPerson类- 判断
HTPerson类与[HTPerson class]相等, 返回True
答案: True
-
re8:
BOOL re8 = [(id)[HTPerson alloc] isMemberOfClass:[HTPerson class]];
isMemberOfClass对象方法,初始值为本类,无遍历操作,直接比较
- 获取
[HTPerson alloc]的本类:HTPerson类- 判断
HTPerson类与[HTPerson class]相等, 返回True
答案: True
附上打印结果
答案
是不是掌握了诀窍之后,瞬间无对手了?
别飘? 咱们加设一题
BOOL re9 = [(id)[HTPerson class] isKindOfClass:[NSObject class]];
BOOL re10 = [(id)[HTPerson alloc] isKindOfClass:[NSObject class]];
BOOL re11 = [(id)[HTPerson class] isMemberOfClass:[NSObject class]];
BOOL re12 = [(id)[HTPerson alloc] isMemberOfClass:[NSObject class]];
HTLog(@" re9 :%hhd re10 :%hhd re11 :%hhd re12 :%hhd",re9,re10,re11,re12);
记住我每个答案上第一行解题思路。
是什么函数?对象方法还是类方法? 初始值是元类还是本类? 是遍历superclass还是直接比较?
开始答题:
-
re9:
BOOL re9 = [(id)[HTPerson class] isKindOfClass:[NSObject class]];
isKindOfClass类方法,调用objc_opt_isKindOfClass,初始值为元类,拥有遍历操作
- 获取
[HTPerson class]中isa指向的类:HTPerson元类- 判断
HTPerson元类与NSObject类,不相等。- 继续层层寻找
HTPerson元类的superclass- 依次找到:
NSObject元类、NSObject类。- 到
NSObject类时相等,返回True
答案: True
-
re10:
BOOL re10 = [(id)[HTPerson alloc] isKindOfClass:[NSObject class]];
isKindOfClass对象方法,调用objc_opt_isKindOfClass,初始值为本类,有遍历操作
- 获取
[HTPerson alloc]的本类:HTPerson类- 判断
HTPerson类与[NSObject class]不相等。- 继续寻找
HTPerson类的superclass:NSObject类- 此时
NSObject类与[NSObject class]``相等,返回True
答案: True
-
re11:
BOOL re11 = [(id)[HTPerson class] isMemberOfClass:[NSObject class]];
isMemberOfClass类方法,初始值为元类,无遍历操作,直接比较
- 获取
[HTPerson class]中isa指向的类:HTPerson元类- 判断
HTPerson元类与NSObject类,不相等。返回False
答案: False
-
re12:
BOOL re12 = [(id)[HTPerson alloc] isMemberOfClass:[NSObject class]];
isMemberOfClass实例方法,初始值为本类,无遍历操作,直接比较
- 获取
[HTPerson alloc]的本类:HTPerson类- 判断
HTPerson类与NSObject类,不相等。返回False
答案: False
附上打印结果
答案
恭喜你,挑战成功!
别吐槽,面试怎么虐得爽就怎么来 😂
我自己写的时候也很凌乱,直到整理出的解题思路,才打通
任督二脉:是
什么函数?对象方法还是类方法? 初始值是元类还是本类? 是遍历superclass还是直接比较?































