上一篇文章 介绍了如何获取普通OC对象(FBObjectiveCObject
)的所有强引用对象。本文继续介绍如何获取一种特殊OC对象 – NSTimer
的所有强引用对象。
在 FBRetainCycleDetector 的源码中,NSTimer
对应的类为 FBObjectiveCNSCFTimer
。
FBRetainCycleDetector 源码地址: https://github.com/facebook/FBRetainCycleDetector
NSTimer对象
1 | /** |
从类声明中可以看到,FBObjectiveCNSCFTimer
继承自上一篇文章提到的普通OC对象 FBObjectiveCObject
,这也很好理解,在 Foundation 框架中,NSTimer
也是继承自 NSObject
的。
关于 NSTimer
,相信大家都知道它会强引用 target 。但是 NSTimer
并没有提供获取 target 的方法,我们该如何在运行时获取其引用的具体 target 对象呢?
1 | + (NSTimer *)timerWithTimeInterval:(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 | typedef struct { |
初步看起来好像并没有什么可用的信息。我们来挨个字段研究下。
- version 没啥可说的,就是个版本标识。
- info 是一个隐藏了具体类型的指针,按苹果的代码风格,估计里面藏了什么秘密!这个 info 字段就是下面的 retain 、 release 、 copyDescription 字段中的参数。
- 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 | typedef struct { |
经过一番折腾,终于能拿到具体的 target 了!
1 | _FBNSCFTimerInfoStruct infoStruct = *(_FBNSCFTimerInfoStruct *)(context.info); |
获取 userInfo
除了 target 之外,还有一个字段被我们忽略了 – userInfo 字段。NSTimer
不仅会持有 target ,同时也会持有 userInfo 对象。
所以,我们要把 userInfo 也加入到被引用对象列表中。
1 | if (infoStruct.userInfo) { |
汇总
NSTimer
对象的所有强引用对象,除了继承父类 NSObject
的所有强引用对象之外,还包括 target 和 userInfo 对象。
FBRetainCycleDetector 中 FBObjectiveCNSCFTimer
的源码也比较简洁,直接上全部代码:
1 | - (NSSet *)allRetainedObjects |
总结
FBRetainCycleDetector 将OC对象划分为3类,分别为:
- FBObjectiveCObject
- FBObjectiveCNSCFTimer
- FBObjectiveCBlock
上一篇文章 研究了 FBObjectiveCObject
的源码,介绍了如何获取 NSObject
的所有强引用对象;
本文通过研究 FBObjectiveCNSCFTimer
的源码,介绍了如何获取 NSTimer
的所有强引用对象。
最后一篇文章会继续研究 FBObjectiveCBlock
的源码,探索如何获取 Block
的所有强引用对象。