上一节我们分析了类的结构
,成员变量
、属性
、实例方法
和类方法
的读取。
今天我深入拓展一下:
- 怎么知道成员变量、属性存放位置的?
-
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静态文件。打开
image.pngmain.cpp
文件,搜索hobby
image.png
image.png因为是
特殊名称
(系统中基本不会出现的名称),所以一下就可以搜索出来。上面就是这个名称出现的所有位置。 我们对比
image.pngobjc_class
的data()
数据格式来看:
发现
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_copyMethodListclass_copyMethodList
:大致意思是说:
- 如果这个类有
实现
了的实例函数
,就返回一个包含所有实例函数的数组
。最后你必须free
释放这个数组。- 如果
当前类
没有实例函数
,或者当前类为空
,返回Null
.并且outCount
为0
进入objc4源码中,进入
image.pngclass_copyMethodList
方法。
我们可以看到完整的流程。 从
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进入
image.pnglookUpImpOrForward
。 寻找imp
_class_getMethod
- 进入
_class_getMethod
image.png
- 进入
getMethod_nolock
通过熟悉源码和文档,我们可以知道:
-
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
image.png
- 不同的是这里入参是
getMeta
, 我们进入查看:发现个
有意思
的事情。 我们传入cls
- 代码判断
isMetaClass
是否为元类
,如果是元类
就返回cls
本类,如果不是, 我就返回本类的isa
。用大白话解释:
兄弟,类方法
都放在元类
里。如果你给我的是元类
,我可以直接操作
。 如果不是元类
,我就免费帮你转成元类
(类的isa指向元类),再去操作
。总之,我这只办理元类
的业务。
- 在
C
和C++
的层面不存在类方法
和对象方法
,一切方法的处理,在它确定对象
(类还是元类)之后,都交给class_getInstanceMethod
来处理。 所以后续方法就跟问题2
的处理一样了。
因为这题是获取类方法。 题中类方法只有sayHappy
,所以第一个
和第二个
都是不存在
。 类方法是存在元类中,所以第四个
是存在
的。
- 那
第三个
呢? 第三个上面说了,class_getClassMethod
只处理类方法
,如果不是元类,就帮你转成元类。 - 所以
第三个
实际上是转成了元类
,再调用
了元类
中储存的类方法
。
所以打印结果如下:
image.png- 问题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
的,所以是存在
的。 -
第二个
和第三个
,我们会调用消息转发
机制。
我们打印查看,最终都是存在的。
image.png
总结
-
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.png image.png
源码分析
image.pngisKindOfClass:
类方法
: 类的Isa
指向的是元类
,沿着父类(superclass)
这条线寻找,检查是否存在和入参cls
相等的类
。实例方法
: 对象的Isa
指向的是本类
,沿着父类(superclass)
这条线寻找,检查是否存在和入参cls
相等的类
。isMemberOfClass
类方法
: 判断Isa
指向的元类
,是否与入参cls
相等。实例方法
: 判断Isa
指向的本类
是否与入参cls
相等。
💣 💥
当我们以为一切仅在掌控之中
时,却发现isKindOfClass
的类方法
和实例方法
压根没调用😭
加入
image.png断点
打开
image.png image.png汇编模式
image.png
- 为什么要调用
objc_opt_class
呢?我们在objc
源码层找不到调用依据
,打开llvm
搜索objc_opt_isKindOfClass
。我们看到了熟悉的
objc_alloc
。你是否记得NSObject
的alloc方法也没走objc4
源码的alloc
类方法? 而是llvm
在编译层就将其处理好了。image.png
在这个表中,
objc_opt_isKindOfClass
静静地跟着objc_alloc
一起躺着,我们看注释,可以知道苹果官方
设计的原因,因为这些函数极少被改变
,所以为了加速性能
,苹果在llvm编译层就已经将其优化处理。 比如isKindOfClass
如果没有被外部重写,在被调用时都是直接消息转发
执行objc_opt_isKindOfClass
。
objc4
源码中查看objc_opt_isKindOfClass
内部实现:发现内部实现就跟
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
还是直接比较
?
网友评论