runtime 分类结构体_Runtime(五)类的判定

本文详细探讨了Objective-C中`super`关键字的底层实现,包括从C++代码、Apple开发文档、源码分析、LLDB调试、LLVM中间代码和汇编代码等多个角度进行解析。同时,文章介绍了`isKindOfClass:`和`isMemberOfClass:`这两个类判定方法的工作原理,并通过实例展示了它们在类层次结构中的应用。
摘要由CSDN通过智能技术生成

我们经常需要在开发中判定某一个类,比如下面场景:

判定在某一个页面:isMemberOfClass来指定只有在某页面下的操作。

判断是否某个类,用于容错,这很常见。

1

2

3if([BFUserModel isKindOfClass:BFModel]) {

//.....针对user model

}

这些涉及到类的判定的,我们下面会从一个关键字和两个方法去阐述。

super关键字:调用父类方法的关键字

isMemberOfClass方法:判断某个instance(class)是否是对应的class(meta-class)对象

isKindOfClass方法:判断某个instance(class)是否是对应的class(meta-class)继承体系下的对象。

除了,需要关注本文讲的内容。还需关注的是,本文将iOS底层挖掘的方式大致都展现了。

一、super

super平时开发中,调用的非常频繁,在viewcontroller以及view的生命各个周期方法,我们都会先调用父类的对应实现。

下面我们根据实例,代码在这儿。去分析super的含义。

我们定义下面两个有继承关系的类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20@interface BFPerson : NSObject

- (void)eat;

@end

@implementation BFPerson

- (void)eat

{

NSLog(@"%s", __func__);

}

@end

//--------------

@interface BFBoy : BFPerson

- (void)eat;

@end

@implementation BFBoy

- (void)eat

{

[super eat];

}

@end

下面是调用代码:

1

2BFBoy *boy = [[BFBoy alloc] init];

[boy eat];

1.1 如何分析

我们下面将会通过各种方式来逐一对super进行剖析。

1.1.1 C++代码分析

先将代码重写为C++代码

1$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BFBoy.m

重写后,下面是-[BFBoy eat]函数的实现(删去了类型转换):

1

2

3

4

5

6

7

8static void _I_BFBoy_eat(BFBoy * self, SEL _cmd) {

(void *)objc_msgSendSuper)(

(__rw_objc_super){

self,

class_getSuperclass(objc_getClass("BFBoy"))

},

sel_registerName("eat"));

}

从上面转换我们可以初步看出:

[super eat]转换后调用了objc_msgSendSuper类的函数,并且其参数比较陌生。是一个结构体,其含义似乎表示的是super结构体。

当然,我们从之前历次的分析来看,重写其实并不完全是运行时的行为表现,所以,这种方式仅做参考。

1.1.2 Developer Document登场

之前我们在分析各种各样的问题的时候,其实该方法,或者该工具一直没有正式登场。

下面我们在Xcode中,⌘+⇧+0 调出开发文档。

并在其中搜索上一步提到的objc_msgSendSuper。

以上是,苹果开发文档给出的说明,权威清晰。缺点是,有时候文档并没有说明或者泛泛而谈,甚至不知所云。

1.1.3 源码

同样的,objc 源码也是我们找定义、找方法、找逻辑的最佳选择。

这种方式,只要获取的源码是最新的,其对本质的还原度,最直接、还原度最高。缺点是,阅读源码费时费力费脑。

但阅读源码是基本功,要炉火纯青。

1.1.4 LLDB调试

以上都是从文档或者未运行的转译代码中,对代码本质的挖掘,如果以上过程能完成,基本对本质了解已经七七八八。

但是,OC是一门动态性强大的语言,所以,一切以运行时为准。

我们还需要观察运行时的状态。

至于如何运用LLDB进行调试,可以查看待补-iOS调试。

1.1.5 LLVM中间代码分析

这是一种全新的方式,是在编译过程中,产生的中间代码,来对中间代码进行剖析。

那么编译过程是如何产生中间代码的?

如下图:

当然,有了中间代码,我们还要学会看,可参考官方文档。

我们攫取了一些常用的语法:

1.1.6 汇编分析

有时候,苹果没有给出文档,也无相关源码,甚至Debug时,也是毫无头绪,我们可以通过汇编指令级别的分析,来管中窥豹,或许能发现个所以然。

1.1.6.1 Perform Action

下面是对应的汇编源码:

1.1.6.2 Debug Workflow

下面是调试时的汇编指令:

结合上面两种汇编方式查看,我们最终确定:

super在底层,最终调用的方法是:objc_msgSendSuper2。

1.2 super的本质

经过上面层层剥开,我们现在能确认objc_msgSendSuper2,是理解super的关键。

而objc_msgSendSuper2两个参数,对我们的理解又至关重要:

1

2

3id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super,

SEL _Nonnull op,

...);

其中objc_super:

1

2

3

4struct objc_super {

__unsafe_unretained _Nonnull id receiver;

__unsafe_unretained _Nonnull Class super_class;

};

根据上面C++重写后的代码:

1

2

3

4

5

6

7

8static void _I_BFBoy_eat(BFBoy * self, SEL _cmd) {

(void *)objc_msgSendSuper)(

(__rw_objc_super){

self,

class_getSuperclass(objc_getClass("BFBoy"))

},

sel_registerName("eat"));

}

receiver就是self本身,即BFBoy实例对象boy,它表示消息的接收者仍然是boy,即仍然是子类对象。

super_class为BFBoy的父类BFPeron,它表示,要从父类开始查找对应的方法。

综上所述,总结为下图:

1.3 小试身手—实例测试

在了解了super对应的原理之后,我们就看一个小实例。

1

2

3

4

5

6

7

8

9

10

11@implementation BFBoy

- (instancetype)init {

if (self = [super init]) {

NSLog(@"[self class] = %@", [self class]);

NSLog(@"[self superclass] = %@", [self superclass]);

NSLog(@"[super class] = %@", [super class]);

NSLog(@"[super superclass] = %@", [super superclass]);

}

return self;

}

@end

在BFBoy的初始化方法中,上面打印的分别是什么?

结果如下:

和你预想的一致吗?

我们仍然对这个进行一些分析:

super相关的调用转化为objc_msgSendSuper2调用;

objc_msgSendSuper2开始从BFPerson查找class/superclass方法;

一直找到NSObject,调用NSObject的class/superclass方法;

我们从objc源码中找到下面实现:

1

2

3

4

5

6

7//NSObject.mm

- (Class)class {

return object_getClass(self);

}

- (Class)superclass {

return [self class]->superclass;

}

假如父类BFPerson未实现的方法,在NSObject实现了,而class/superclass中的self,就是BFBoy实例对象boy本身,那么上面的结果你明白了吗?

二、类的判定

2.1 isMemberOfClass

从NSObject.mm源码中得到:

1

2

3

4

5

6

7+ (BOOL)isMemberOfClass:(Class)cls {

return object_getClass((id)self) == cls;

}

- (BOOL)isMemberOfClass:(Class)cls {

return [self class] == cls;

}

从源码可以得出:

instance对象:判断对应的class对象是否和传入的对象相同。

class对象:判断对应的meta-class对象是否和传入的对象相同。

传入的对象,intance对象、class对象、meta-class对象都可以。

2.2 isKindOfClass1

2

3

4

5

6

7

8

9

10

11

12

13+ (BOOL)isKindOfClass:(Class)cls {

for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {

if (tcls == cls) return YES;

}

return NO;

}

- (BOOL)isKindOfClass:(Class)cls {

for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {

if (tcls == cls) return YES;

}

return NO;

}

instance对象:判断对应的class对象及其父类对象是否和传入的对象相同。

class对象:判断对应的meta-class对象及其父类对象是否和传入的对象相同。

传入的对象,intance对象、class对象、meta-class对象都可以。

2.3 实例1

2

3

4

5

6

7

8

9

10

11

12

13// 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES

NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1

NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0

NSLog(@"%d", [BFPerson isKindOfClass:[BFPerson class]]); // 0

NSLog(@"%d", [BFPerson isMemberOfClass:[BFPerson class]]); // 0

NSLog(@"------------------");

//正常使用实例对象

BFPerson *person = [[BFPerson alloc] init];

NSLog(@"%d", [person isMemberOfClass:[BFPerson class]]);//1

NSLog(@"%d", [person isMemberOfClass:[NSObject class]]);//0

NSLog(@"%d", [person isKindOfClass:[BFPerson class]]);//1

NSLog(@"%d", [person isKindOfClass:[NSObject class]]);//1

上面的输出,有一行需要着重指出:

1NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]);

翻译一下:NSObject类的元类对象,及其该元类对象的父类对象与NSObject类对象相等吗?

第一反应,不相等,元类对象怎么会和类对象相等。但是其结果为1。

如下图:

NSObject元类对象在其寻找父类,找到根元类后,会指向类对象,这是一个陷阱。

三、总结

3.1 super

[super message]的底层实现。

消息接收者仍然是子类对象

从父类开始查找方法的实现

3.2 类的判定

参考

链接

示例代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值