0%

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

内存泄漏检测最佳实践 一文中,我们提到可以使用 FBRetainCycleDetector 获取OC对象的循环引用链。想要获取循环引用链,首先要知道每个OC对象到底引用了哪些对象。

本文通过解读 FBRetainCycleDetector 的源码,介绍如何获取 普通OC对象 的所有强引用对象。

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

OC对象类型

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

  • FBObjectiveCObject
  • FBObjectiveCNSCFTimer
  • FBObjectiveCBlock

其中,FBObjectiveCNSCFTimer 继承自 FBObjectiveCObject,表示 NSTimer 对象,FBObjectiveCBlock 表示OC Block 对象。这两类OC对象会在接下来的文章中继续探讨。本文先介绍普通OC对象 – FBObjectiveCObject

普通OC对象

先看 FBObjectiveCObject 的定义:

1
2
3
4
5
6
7

/**
FBObjectiveCGraphElement specialization that can gather all references kept in ivars, as part of collection
etc.
*/
@interface FBObjectiveCObject : FBObjectiveCGraphElement
@end

FBObjectiveCObject(包括 FBObjectiveCNSCFTimerFBObjectiveCBlock )继承自 FBObjectiveCGraphElement ,表示这些对象都可作为对象引用图的一个节点。 FBRetainCycleDetector 正是通过对这些节点的遍历,来找出循环引用链的。

强引用对象

那么,普通OC对象会通过哪些方式持用其他对象呢?

  • ivar 成员变量
  • 集合类中的元素,比如 NSArray、NSCollection 中的元素
  • associatedObject

我们只需要针对这几种情况分别处理即可。

注意:通过这三种方式添加的引用都有可能是 弱引用 ,实际处理时需要将弱引用的情况排除掉。

ivar 成员变量

在OC中,我们可以直接添加一个 ivar ,也可以通过声明 property 的方式添加 ivarivarproperty 都能够以弱引用的方式添加。

1
2
3
4
5
6
7
@interface DemoClass () {
NSObject *_strongIvar;
__weak NSObject *_weakIvar;
}
@property(nonatomic, strong) NSObject *strongProperty;
@property(nonatomic, weak) NSObject *weakProperty;
@end

如何获取所有 强引用ivar 呢?

  1. 找出所有 ivar
  2. 筛选强引用的 ivar

找出所有 ivar

OC Runtime 提供了 class_copyIvarList 方法,该方法可以直接返回 Class 的 ivar 列表。

FBRetainCycleDetector 中对应的源码为:(有删减)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];
// 获取 ivar 列表
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
// 封装为 FBIvarReference 对象
for (unsigned int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
[result addObject:wrapper];
}
free(ivars);
return [result copy];
}

筛选强引用的 ivar

ivar 列表里包含了所有强引用和弱引用的 ivar 。比如对于上面示例代码中的 DemoClass 对象, class_copyIvarList 返回的结果包含了:

  • _strongIvar
  • _weakIvar
  • _strongProperty
  • _weakProperty

如何才能筛选出强引用的对象(也就是 _strongIvar_strongProperty )呢?

OC Runtime中还提供了 class_getIvarLayout 方法用于获取所有强引用 ivar 的布局信息;还有一个对应的 class_getWeakIvarLayout 方法用于获取所有弱引用 ivar 的布局信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** 
* Returns a description of the \c Ivar layout for a given class.
*
* @param cls The class to inspect.
*
* @return A description of the \c Ivar layout for \e cls.
*/
OBJC_EXPORT const uint8_t * _Nullable
class_getIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

/**
* Returns a description of the layout of weak Ivars for a given class.
*
* @param cls The class to inspect.
*
* @return A description of the layout of the weak \c Ivars for \e cls.
*/
OBJC_EXPORT const uint8_t * _Nullable
class_getWeakIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

所以,拿到 ivar 列表之后,我们只需遍历一遍所有的 ivar ,判断该 ivar 对应的布局位置是否为强引用即可。

FBRetainCycleDetector 中对应的源码为:(有删减)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static NSArray<id<FBObjectReference>> *FBGetStrongReferencesForClass(Class aCls) {
// 获取 ivar 列表
NSArray<id<FBObjectReference>> *ivars = FBGetClassReferences(aCls);
// 获取强引用 ivar 布局信息
const uint8_t *fullLayout = class_getIvarLayout(aCls);
if (!fullLayout) {
return @[];
}
NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);

// 遍历 ivar,确认是否属于强引用
NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];

return filteredIvars;
}

总结: 通过 class_copyIvarListclass_getIvarLayout ,可以获取所有的强引用 ivar

集合类中的元素

我们平常使用的 NSArrayNSDictionary 默认对元素都是强引用,对于这些集合类,直接遍历其元素就能够获取所有其强引用的所有对象。

但是现实总是比预想的要复杂。还有一些集合类,比如 NSMapTable 等,可以自定义对元素的引用类型,可以分别设置 keyvalue 使用弱引用还是强引用。

1
2
3
4
5
// NSMapTable.h
+ (id)mapTableWithStrongToStrongObjects;
+ (id)mapTableWithWeakToStrongObjects;
+ (id)mapTableWithStrongToWeakObjects;
+ (id)mapTableWithWeakToWeakObjects;

有没有办法能知道这些集合类的对元素的引用到底是弱引用还是强引用呢?
当然可以。在 NSMapTable 头文件中,我们能找到 keyPointerFunctionsvaluePointerFunctions 这两个方法,通过这两个方法,我们就能知道 NSMapTablekeyvalue 到底是弱引用还是强引用了。

1
2
3
4
// NSMapTable.h
/* return an NSPointerFunctions object reflecting the functions in use. This is a new autoreleased object that can be subsequently modified and/or used directly in the creation of other pointer "collections". */
@property (readonly, copy) NSPointerFunctions *keyPointerFunctions;
@property (readonly, copy) NSPointerFunctions *valuePointerFunctions;

FBRetainCycleDetector 中对应的源码为:(有删减)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// NSArray/NSDictionary/NSMapTable 等集合类均遵循 NSFastEnumeration 协议
if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {
// key 是否为强引用
BOOL retainsKeys = [self _objectRetainsEnumerableKeys];
// value 是否为强引用
BOOL retainsValues = [self _objectRetainsEnumerableValues];
BOOL isKeyValued = NO;
if ([aCls instancesRespondToSelector:@selector(objectForKey:)]) {
isKeyValued = YES;
}
// ...
for (id subobject in self.object) {
if (retainsKeys) {
// key 为强引用,获取所有 key
FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, subobject, self.configuration);
}
if (isKeyValued && retainsValues) {
// value 为强引用,获取所有value
FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, [self.object objectForKey:subobject], self.configuration);
}
}
// ...
}

具体判断 keyvalue 是否为强引用的代码为:

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
35
36
37
38
39
40
41
42
43
44
45
//  是否强引用 value
- (BOOL)_objectRetainsEnumerableValues
{
if ([self.object respondsToSelector:@selector(valuePointerFunctions)]) {
NSPointerFunctions *pointerFunctions = [self.object valuePointerFunctions];
if (pointerFunctions.acquireFunction == NULL) {
return NO;
}
if (pointerFunctions.usesWeakReadAndWriteBarriers) {
return NO;
}
}
// 默认为强引用(如 NSArray)
return YES;
}

// 是否强引用key
- (BOOL)_objectRetainsEnumerableKeys
{
if ([self.object respondsToSelector:@selector(pointerFunctions)]) {
// NSHashTable and similar
// If object shows what pointer functions are used, lets try to determine
// if it's not retaining objects
NSPointerFunctions *pointerFunctions = [self.object pointerFunctions];
if (pointerFunctions.acquireFunction == NULL) {
return NO;
}
if (pointerFunctions.usesWeakReadAndWriteBarriers) {
// It's weak - we should not touch it
return NO;
}
}

if ([self.object respondsToSelector:@selector(keyPointerFunctions)]) {
NSPointerFunctions *pointerFunctions = [self.object keyPointerFunctions];
if (pointerFunctions.acquireFunction == NULL) {
return NO;
}
if (pointerFunctions.usesWeakReadAndWriteBarriers) {
return NO;
}
}
// 默认为强引用(如 NSDictionary)
return YES;
}

总结: 通过遍历集合元素(包括 keyvalue ),并判断其是否为强引用,可以获取集合类强引用的所有元素。

associatedObject

除了上述两种常规的引用类型,在OC中我们还可以通过 objc_setAssociatedObject 在运行时为OC对象动态添加引用对象。同样,通过这种方式添加的对象也不一定是强引用对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

其中的 policy 用于设置引用类型,有如下取值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Associative References */

/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};

显然,OBJC_ASSOCIATION_RETAIN_NONATOMICOBJC_ASSOCIATION_RETAIN 为强引用类型。

hook 调用

由于 objc_setAssociatedObject 是在运行时为OC对象动态添加引用,我们需要hook掉 objc_setAssociatedObject 方法调用,将运行时添加的强引用对象记录下来。

怎么hook呢? objc_setAssociatedObject 为C方法,只能派 fishhook 上场了。

FBRetainCycleDetector 中对应的源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// FBAssociationManager.m
+ (void)hook
{
#if _INTERNAL_RCD_ENABLED
std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);
rcd_rebind_symbols((struct rcd_rebinding[2]){
{
"objc_setAssociatedObject",
(void *)FB::AssociationManager::fb_objc_setAssociatedObject,
(void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject
},
{
"objc_removeAssociatedObjects",
(void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,
(void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects
}}, 2);
FB::AssociationManager::hookTaken = true;
#endif //_INTERNAL_RCD_ENABLED
}

总结

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

  • FBObjectiveCObject
  • FBObjectiveCNSCFTimer
  • FBObjectiveCBlock

本文通过研究 FBObjectiveCObject 的源码,介绍如何获取 NSObject 的所有强引用对象;
接下来的文章会继续研究 FBObjectiveCNSCFTimerFBObjectiveCBlock 的源码,介绍如何获取 NSTimerBlock 的所有强引用对象。