引用计数是一种经典的内存管理垃圾回收机制,但它最明显的反作用就是循环引用,致使内存泄漏。循环引用实际上是一个闭环。算法
从图论的角度来讲,闭环,其实就是一个有向有环图。图中的顶点表示一个对象,每一条边表示对象之间的关系。若图中的每条边都是有方向的,则称为有向图。 若是图中存在一个顶点,从该顶点出发通过若干条边能够回到该点,则这个图是一个有向有环图。这个环咱们暂且称它为闭环。网络
除了引用计数内存管理,在工程应用中,不少地方都存在着闭环的影子:架构
判断一个有向图是否存在环,有多种算法。app
>=2
,基于此能够找到环:
facebook最近刚开源的FBRetainCycleDetector就是使用深度优先搜索来检测iOS应用在运行时是否存在循环应用。它利用ivar layout 找出对象所引用的全部对象,并用有向图表示,节点就是对象,边就是对象之间的引用。而后使用深度优先搜索,检测出其中是否存在循环引用,精简过的核心代码以下:spa
[stack addObject:wrappedObject];
while ([stack count] > 0) {
@autoreleasepool {
FBNodeEnumerator *top = [stack lastObject];
[objectsOnPath addObject:top];
FBNodeEnumerator *firstAdjacent = [top nextObject];
if (firstAdjacent) {
BOOL shouldPushToStack = NO;
if ([objectsOnPath containsObject:firstAdjacent]) {
// 若是路径中存在访问过的点,说明存在环,代表有内存泄漏隐患
} else {
shouldPushToStack = YES;
}
if (shouldPushToStack) {
[stack addObject:firstAdjacent];
}
} else {
[stack removeLastObject];
[objectsOnPath removeObject:top];
}
}
}复制代码
架构的设计当中,大部分场景下, 应当避免闭环。模块之间的关系应当是线性的,或者树状的,而不该该是存在闭环的复杂网络状。树状的结构意味着系统的层次清晰,模块职责明确。典型的在线服务三层架构图以下:架构设计
这就是一个很简单的树状图,模块之间划分得很是清晰。流行的MVC和MVVP其实也是帮咱们梳理这种层次结构。MVC三者的引用关系以下(Controller引用View和Model,View引用Model):设计
这是一个无环图,假设在图中增长一条从Model到Controller的边,就造成了一个闭环。若是你的Model层或者View层代码中出现了相似这样的代码:import "Controller.h"
,请再审视一遍你的代码设计。 模块之间相互引用,意味着模块之间耦合性高,设计的抽象还不够。在平常的实践开发,这种相互引用的引入每每是无意而隐蔽的。可是随着代码库的日益庞大,模块之间的联系日益增多,一旦这种循环引用多了,整个系统的结构将变得无比复杂,杂乱无章。最终系统的结构会变成这样:code
这个系统一旦出现bug,将牵一发而动全身,改bug就是拆了东墙补西墙,长此以往,再也没有人改动这坨代码,项目华丽地走上了推倒重写之路。cdn
那么如何找到模块之间是否存在闭环呢?咱们能够将问题简化成为文件之间是否存在循环import。这样能够写一个检测脚本,协助定位和清理模块之间的关系。对象
让代码库变得庞大并不难,难的是在业务发展过程当中,一直保持架构的简单。牢记KISS原则:Keep It Simple and Stupid.