检测 NSObject 对象持有的强指针
在上一篇文章中介绍了 FBRetainCycleDetector 的基本工作原理,这一篇文章中我们开始分析它是如何从每一个对象中获得它持有的强指针的。
如果没有看第一篇文章这里还是最好看一下,了解一下
FBRetainCycleDetector
的工作原理,如何在 iOS 中解决循环引用的问题。http://t.cn/RtJsLG0
FBRetainCycleDetector 获取对象的强指针是通过 FBObjectiveCObject 类的 - allRetainedObjects 方法,这一方法是通过其父类 FBObjectiveCGraphElement 继承过来的,只是内部有着不同的实现。
allRetainedObjects 方法
我们会以 XXObject 为例演示 - allRetainedObjects 方法的调用过程:
#import
@interface XXObject : NSObject
@property (nonatomic, strong) id first;
@property (nonatomic, weak) id second;
@property (nonatomic, strong) id third;
@property (nonatomic, strong) id forth;
@property (nonatomic, weak) id fifth;
@property (nonatomic, strong) id sixth;
@end
使用 FBRetainCycleDetector 的代码如下:
XXObject *object = [[XXObject alloc] init];
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:object];
__unused NSSet *cycles = [detector findRetainCycles];
在 FBObjectiveCObject 中,- allRetainedObjects 方法只是调用了 - _unfilteredRetainedObjects,然后进行了过滤,文章主要会对 - _unfilteredRetainedObjects 的实现进行分析:
- (NSSet *)allRetainedObjects {
NSArray *unfiltered = [self _unfilteredRetainedObjects];
return [self filterObjects:unfiltered];
}
方法 - _unfilteredRetainedObjects 的实现代码还是比较多的,这里会将代码分成几个部分,首先是最重要的部分:如何得到对象持有的强引用:
- (NSArray *)_unfilteredRetainedObjects
NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);
NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];
for (id ref in strongIvars) {
id referencedObject = [ref objectReferenceFromObject:self.object];
if (referencedObject) {
NSArray *namePath = [ref namePath];
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
referencedObject,
self.configuration,
namePath);
if (element) {
[retainedObjects addObject:element];
}
}
}
...
}
获取强引用是通过 FBGetObjectStrongReferences 这一函数:
NSArray> *FBGetObjectStrongReferences(id obj,
NSMutableDictionary> *> *layoutCache) {
NSMutableArray> *array = [NSMutableArray new];
__unsafe_unretained Class previousClass = nil;
__unsafe_unretained Class currentClass = object_getClass(obj);
while (previousClass != currentClass) {
NSArray> *ivars;
if (layoutCache && currentClass) {
ivars = layoutCache[currentClass];
}
if (!ivars) {
ivars = FBGetStrongReferencesForClass(currentClass);
if (layoutCache && currentClass) {
layoutCache[(id)currentClass] = ivars;
}
}
[array addObjectsFromArray:ivars];
previousClass = currentClass;
currentClass = class_getSuperclass(currentClass);
}
return [array copy];
}
上面代码的核心部分是执行 FBGetStrongReferencesForClass 返回 currentClass 中的强引用,只是在这里我们递归地查找了所有父类的指针,并且加入了缓存以加速查找强引用的过程,接下来就是从对象的结构中获取强引用的过程了:
static NSArray> *FBGetStrongReferencesForClass(Class aCls) {
NSArray> *ivars = [FBGetClassReferences(aCls) filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
if ([evaluatedObject isKindOfClass:[FBIvarReference class]]) {
FBIvarReference *wrapper = evaluatedObject;
return wrapper.type != FBUnknownType;
}
return YES;
}]];
const uint8_t *fullLayout = class_getIvarLayout(aCls);
if (!fullLayout) {
return nil;
}
NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);
NSArray> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];
return filteredIvars;
}
该方法的实现大约有三个部分:
调用 FBGetClassReferences 从类中获取它指向的所有引用,无论是强引用或者是弱引用
调用 FBGetLayoutAsIndexesForDescription 从类的变量布局中获取强引用的位置信息
使用 NSPredicate 过滤数组中的弱引用
获取类的 Ivar 数组
FBGetClassReferences 方法主要调用 runtime 中的 class_copyIvarList 得到类的所有 ivar:
这里省略对结构体属性的处理,因为太过复杂,并且涉及大量的C++ 代码,有兴趣的读者可以查看
FBGetReferencesForObjectsInStructEncoding
方法的实现。
NSArray> *FBGetClassReferences(Class aCls) {
NSMutableArray> *result = [NSMutableArray new];
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
for (unsigned int i = 0; i
上述实现还是非常直接的,遍历 ivars 数组,使用 FBIvarReference 将其包装起来然后加入 result 中,其中的类 FBIvarReference 仅仅起到了一个包装的作用,将 Ivar 中保存的各种属性全部保存起来:
typedef NS_ENUM(NSUInteger, FBType) {
FBObjectType,
FBBlockType,
FBStructType,
FBUnknownType,
};
@interface FBIvarReference : NSObject
@property (nonatomic, copy, readonly, nullable) NSString *name;
@property (nonatomic, readonly) FBType type;
@property (nonatomic, readonly) ptrdiff_t offset;
@property (nonatomic, readonly) NSUInteger index;
@property (nonatomic, readonly, nonnull) Ivar ivar;
- (nonnull instancetype)initWithIvar:(nonnull Ivar)ivar;
@end
包括属性的名称、类型、偏移量以及索引,类型是通过类型编码来获取的,在 FBIvarReference 的实例初始化时,会通过私有方法 - _convertEncodingToType: 将类型编码转换为枚举类型:
- (FBType)_convertEncodingToType:(const char *)typeEncoding {
if (typeEncoding[0] == '{') return FBStructType;
if (typeEncoding[0] == '@') {
if (strncmp(typeEncoding, "@?", 2) == 0) return FBBlockType;
return FBObjectType;
}
return FBUnknownType;
}
当代码即将从 FBGetClassReferences 方法中返回时,使用 lldb 打印 result 中的所有元素:
get-ivars
上述方法成功地从 XXObject 类中获得了正确的属性数组,不过这些数组中不止
包含了强引用,还有被 weak 标记的弱引用:
(
[_first, index: 1],
[_second, index: 2],
[_third, index: 3],
[_forth, index: 4],
[_fifth, index: 5],
[_sixth, index: 6]
)
获取 Ivar Layout
当我们取出了 XXObject 中所有的属性之后,还需要对其中的属性进行过滤;那么我们如何判断一个属性是强引用还是弱引用呢?Objective-C 中引入了 Ivar Layout 的概念,对类中的各种属性的强弱进行描述。
它是如何工作的呢,我们先继续执行 FBGetStrongReferencesForClass 方法:
get-ivar-layout
在 ObjC 运行时中的 class_getIvarLayout 可以获取某一个类的 Ivar Layout,而 XXObject 的 Ivar Layout 是什么样的呢?
(lldb) po fullLayout
"\x01\x12\x11"
Ivar Layout 就是一系列的字符,每两个一组,比如 \xmn,每一组 Ivar Layout 中第一位表示有 m 个非强属性,第二位表示接下来有 n 个强属性;如果没有明白,我们以 XXObject 为例演示一下:
@interface XXObject : NSObject
@property (nonatomic, strong) id first;
@property (nonatomic, weak) id second;
@property (nonatomic, strong) id third;
@property (nonatomic, strong) id forth;
@property (nonatomic, weak) id fifth;
@property (nonatomic, strong) id sixth;
@end
第一组的 \x01 表示有 0 个非强属性,然后有 1 个强属性 first
第二组的 \x12 表示有 1 个非强属性 second,然后有 2 个强属性 third forth
第三组的 \x11 表示有 1 个非强属性 fifth, 然后有 1 个强属性 sixth
在对 Ivar Layout 有一定了解之后,我们可以继续对 FBGetStrongReferencesForClass 分析了,下面要做的就是使用 Ivar Layout 提供的信息过滤其中的所有非强引用,而这就需要两个方法的帮助,首先需要 FBGetMinimumIvarIndex 方法获取变量索引的最小值:
static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
NSUInteger minimumIndex = 1;
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
if (count > 0) {
Ivar ivar = ivars[0];
ptrdiff_t offset = ivar_getOffset(ivar);
minimumIndex = offset / (sizeof(void *));
}
free(ivars);
return minimumIndex;
}
然后执行 FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout) 获取所有强引用的 NSRange:
static NSIndexSet *FBGetLayoutAsIndexesForDescription(NSUInteger minimumIndex, const uint8_t *layoutDescription) {
NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
NSUInteger currentIndex = minimumIndex;
while (*layoutDescription != '\x00') {
int upperNibble = (*layoutDescription & 0xf0) >> 4;
int lowerNibble = *layoutDescription & 0xf;
currentIndex += upperNibble;
[interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
currentIndex += lowerNibble;
++layoutDescription;
}
return interestingIndexes;
}
因为高位表示非强引用的数量,所以我们要加上 upperNibble,然后 NSMakeRange(currentIndex, lowerNibble) 就是强引用的范围;略过 lowerNibble 长度的索引,移动 layoutDescription 指针,直到所有的 NSRange 都加入到了 interestingIndexes 这一集合中,就可以返回了。
过滤数组中的弱引用
在上一阶段由于已经获取了强引用的范围,在这里我们直接使用 NSPredicate 谓词来进行过滤就可以了:
NSArray> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];
filtered-ivars
====
接下来,我们回到文章开始的 - _unfilteredRetainedObjects 方法:
- (NSSet *)allRetainedObjects {
NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);
NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];
for (id ref in strongIvars) {
id referencedObject = [ref objectReferenceFromObject:self.object];
if (referencedObject) {
NSArray *namePath = [ref namePath];
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
referencedObject,
self.configuration,
namePath);
if (element) {
[retainedObjects addObject:element];
}
}
}
...
}
FBGetObjectStrongReferences 只是返回 id 对象,还需要 FBWrapObjectGraphElementWithContext 把它进行包装成 FBObjectiveCGraphElement:
最后会把封装好的实例添加到 retainedObjects 数组中。
- _unfilteredRetainedObjects 同时也要处理集合类,比如数组或者字典,但是如果是无缝桥接的 CF 集合,或者是元类,虽然它们可能遵循 NSFastEnumeration 协议,但是在这里并不会对它们进行处理:
- (NSArray *)_unfilteredRetainedObjects {
...
if ([NSStringFromClass(aCls) hasPrefix:@"__NSCF"]) {
return retainedObjects;
}
if (class_isMetaClass(aCls)) {
return nil;
}
...
}
在遍历内容时,Mutable 的集合类中的元素可能会改变,所以会重试多次以确保集合类中的所有元素都被获取到了:
- (NSArray *)_unfilteredRetainedObjects {
...
if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {
NSInteger tries = 10;
for (NSInteger i = 0; i
这里将遍历集合中的元素的代码放入了 @try 中,如果在遍历时插入了其它元素,就会抛出异常,然后 continue 重新遍历集合,最后返回所有持有的对象。
最后的过滤部分会使用 FBObjectGraphConfiguration 中的 filterBlocks 将不需要加入集合中的元素过滤掉:
总结
FBRetainCycleDetector 在对象中查找强引用取决于类的 Ivar Layout,它为我们提供了与属性引用强弱有关的信息,帮助筛选强引用。
检测 NSObject 对象持有的强指针的更多相关文章
- Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6786239 Android 系统的运行时库层代 ...
- ZT自老罗的博客 Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 分类: Android 2011-09-23 00:59 31568人阅读 评论(42) 收藏 举报 androidclass ...
- (转载)OC学习篇之---Foundation框架中的NSObject对象
前一篇文章讲到了OC中的代理模式,而且前几篇文章就介绍了OC中的类相关知识,从这篇文章开始我们开始介绍Foundation框架. OC中的Foundation框架是系统提供了,他就相当于是系统的一套a ...
- Android系统智能指针的设计思路(轻量级指针、强指针、弱指针)
本博客为原创,转载请注明出处,谢谢. 参考博文:Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 C++中最容易出错的地方莫过于指针了,指针问题主要有两类,一是内存泄露,二是无 ...
- 探索一个NSObject对象占用多少内存?
1 下面写代码测试探索NSObject的本质 Objective-C代码,底层实现其实都是C\C++代码 #import <Foundation/Foundation.h> int mai ...
- IOS 强指针(strong)和弱指针(weak)
// strong 强指针 // weak 弱指针 // ARC, 只要对象没有强指针就会自动释放 // OC中默认都是强指针
- OC学习篇之---Foundation框架中的NSObject对象
从这篇文章开始我们开始介绍Foundation框架. OC中的Foundation框架是系统提供了,他就相当于是系统的一套api,和Java中的一些系统jar很相似,又早起的一批人开发的,内部有很多现 ...
- 把vector中的string对象导入到字符指针数组中
#include <iostream>#include <string>#include <vector>//#include <cctype>#inc ...
- [Effective C++ --017]以独立语句将newed对象置入智能指针
这一节也比较简单,先假设我们有如下的函数: int foo(); void memFoo(shared_ptr<T> pw, int foo); 现在假设我们要调用memFoo函数: me ...
随机推荐
- NopCommerce架构分析之三---数据库初试化及数据操作
系统启动时执行任务:IStartupTask,启动时执行的任务主要是数据库的初始化和加载. IStartupTask调用IEfDataProvider进行数据库的初始化. IEfDataProvide ...
- [转]MVC之 过滤器(Filter)
一.自定义Filter 自定义Filter需要继承ActionFilterAttribute抽象类,重写其中需要的方法,来看下ActionFilterAttribute类的方法签名. //表示所有 ...
- HUST 1017 Exact cover dance links
学习:请看 www.cnblogs.com/jh818012/p/3252154.html 模板题,上代码 #include<cstdio> #include<cstring> ...
- Obective-C之宏定义
优(dan)美(teng)的前奏 宏定义这个东东,估计大家在代码中应该天天用吧. 在我刚刚做的一个项目中,各种往代码里码“#define”这种预处理指令. 什么动画时长啊,cell高度啊,cell的个 ...
- microsoft的罗马帝国——浪潮之巅
其实开始读微软的这篇已经比较久了,从来学校的前一天晚上等车的时候就开始读了,直到今天才看完.嗯,微软的确是个帝国. 那就从头开始讲把,关于帝国的传奇都是比较长的故事呢.至于我的叙述水平和我的知识水平都 ...
- Android实例-TTabControl的使用(XE8+小米2)
结果: 1.如果直接改变Tab的TabIndex,那样是没有动态效果的.如果想要动态效果需要用到ChangeTabAction1; 2.ChangeTabAction1可以直接为按钮指定Action ...
- Android实例-使用自定义字体文件(XE8+小米2)
结果: 1.需要修改DELPHI自身的FMX.FontGlyphs.Android.pas,复制到程序的根目录下(红色部分为修改过的). 2.字体文件从 C:\Windows\Fonts 直接拷贝到A ...
- 转载SSIS中的容器和数据流—举例说明数据转换任务
在上一个随笔中我们熟悉了数据流任务,现在来做一个例子,通过实践学习这些介绍的内容.这个例子从AdventureWorks数据库中取得数据,然后对数据进行聚合,排序,计算产生新列操作并输入到一个.csv ...
- HTTP笔记:URI与URL
URI与URL 简单理解是这样的:理解URI和URL的区别,我们引入URN这个概念.URI = Universal Resource Identifier 统一资源标志符URL = Universal ...
- ECSHOP在线手册布局参考图--积分商城 exchange_list.dwt
A.购物车 1,设置方法 程序自动读取购物车的商品数量 2,代码相关 cart.lbi 中 {insert_scripts files='transport.js'} <div clas ...