0%

获取OC对象的所有强引用对象(二)

上一篇文章 介绍了如何获取普通OC对象(FBObjectiveCObject)的所有强引用对象。本文继续介绍如何获取一种特殊OC对象 – NSTimer 的所有强引用对象。

FBRetainCycleDetector 的源码中,NSTimer 对应的类为 FBObjectiveCNSCFTimer

FBRetainCycleDetector 源码地址: https://github.com/facebook/FBRetainCycleDetector

NSTimer对象

1
2
3
4
5
6
7
/**
Specialization of FBObjectiveCObject for NSTimer.
Standard methods that FBObjectiveCObject uses will not fetch us all objects retained by NSTimer.
One good example is target of NSTimer.
*/
@interface FBObjectiveCNSCFTimer : FBObjectiveCObject
@end

从类声明中可以看到,FBObjectiveCNSCFTimer 继承自上一篇文章提到的普通OC对象 FBObjectiveCObject ,这也很好理解,在 Foundation 框架中,NSTimer 也是继承自 NSObject 的。

关于 NSTimer ,相信大家都知道它会强引用 target 。但是 NSTimer 并没有提供获取 target 的方法,我们该如何在运行时获取其引用的具体 target 对象呢?

1
2
3
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

获取 target

NSTimer 没有直接提供获取 target 的方法,但是我们可以通过 CFRunloop 曲线救国。
CFRunloop.h 中提供了一个获取 NSTimer 执行上下文的方法。

1
CF_EXPORT void CFRunLoopObserverGetContext(CFRunLoopObserverRef observer, CFRunLoopObserverContext *context);

CFRunLoopTimerContext 结构体

通过上下文( context )我们能获取哪些信息呢?继续看 CFRunLoopObserverContext 的定义。

1
2
3
4
5
6
7
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopTimerContext;

初步看起来好像并没有什么可用的信息。我们来挨个字段研究下。

  • version 没啥可说的,就是个版本标识。
  • info 是一个隐藏了具体类型的指针,按苹果的代码风格,估计里面藏了什么秘密!这个 info 字段就是下面的 retainreleasecopyDescription 字段中的参数。
  • retain 是一个函数指针,用于对 info 执行 retain 操作。
  • relase 是一个函数指针,用于对 info 执行 release 操作。
  • copyDescription 是一个函数指针,用于生成一个字符串描述。

version 和其他几个函数指针没什么提供不了什么有效信息,我们还是得从 info 字段入手。
先看下 info 字段的文档。

1
An arbitrary pointer to program-defined data, which can be associated with the message port at creation time. This pointer is passed to all the callbacks defined in the context.

能看出 info 字段里确实存储着程序设置进去的数据,但是还是没说具体类型!OC Runtime 代码也没有找到蛛丝马迹。只能直接相信 FBRetainCycleDetector 的源码了。

_FBNSCFTimerInfoStruct 结构体

FBRetainCycleDetector 源码里把 info 定义为如下的结构体。(
不知道 FBRetainCycleDetector 是从哪里获取的信息,可能是 猜测 + 运行时验证 得到的吧。)

1
2
3
4
5
6
typedef struct {
long _unknown; // This is always 1
id target;
SEL selector;
NSDictionary *userInfo;
} _FBNSCFTimerInfoStruct;

经过一番折腾,终于能拿到具体的 target 了!

1
2
3
4
5
6
7
_FBNSCFTimerInfoStruct infoStruct = *(_FBNSCFTimerInfoStruct *)(context.info);
if (infoStruct.target) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.target, self.configuration, @[@"target"]);
if (element) {
[retained addObject:element];
}
}

获取 userInfo

除了 target 之外,还有一个字段被我们忽略了 – userInfo 字段。NSTimer 不仅会持有 target ,同时也会持有 userInfo 对象。
所以,我们要把 userInfo 也加入到被引用对象列表中。

1
2
3
4
5
6
if (infoStruct.userInfo) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.userInfo, self.configuration, @[@"userInfo"]);
if (element) {
[retained addObject:element];
}
}

汇总

NSTimer 对象的所有强引用对象,除了继承父类 NSObject 的所有强引用对象之外,还包括 targetuserInfo 对象。

FBRetainCycleDetector 中 FBObjectiveCNSCFTimer 的源码也比较简洁,直接上全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (NSSet *)allRetainedObjects
{
// Let's retain our timer
__attribute__((objc_precise_lifetime)) NSTimer *timer = self.object;
if (!timer) {
return nil;
}

// 1. 添加作为普通OC对象的所有强引用对象,见 *FBObjectiveCObject.m*
NSMutableSet *retained = [[super allRetainedObjects] mutableCopy];

// 获取timer执行上下文
CFRunLoopTimerContext context;
CFRunLoopTimerGetContext((CFRunLoopTimerRef)timer, &context);
// If it has a retain function, let's assume it retains strongly
if (context.info && context.retain) {
_FBNSCFTimerInfoStruct infoStruct = *(_FBNSCFTimerInfoStruct *)(context.info);
// 2. 添加持有的 *target* 对象
if (infoStruct.target) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.target, self.configuration, @[@"target"]);
if (element) {
[retained addObject:element];
}
}
// 3. 添加持有的 *userInfo* 对象
if (infoStruct.userInfo) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.userInfo, self.configuration, @[@"userInfo"]);
if (element) {
[retained addObject:element];
}
}
}
return retained;
}

总结

FBRetainCycleDetector 将OC对象划分为3类,分别为:

  • FBObjectiveCObject
  • FBObjectiveCNSCFTimer
  • FBObjectiveCBlock

上一篇文章 研究了 FBObjectiveCObject 的源码,介绍了如何获取 NSObject 的所有强引用对象;
本文通过研究 FBObjectiveCNSCFTimer 的源码,介绍了如何获取 NSTimer 的所有强引用对象。
最后一篇文章会继续研究 FBObjectiveCBlock 的源码,探索如何获取 Block 的所有强引用对象。