引用計數是一種經典的內存管理垃圾回收機制,但它最明顯的反作用就是循環引用,致使內存泄漏。循環引用實際上是一個閉環。算法
從圖論的角度來講,閉環,其實就是一個有向有環圖。圖中的頂點表示一個對象,每一條邊表示對象之間的關係。若圖中的每條邊都是有方向的,則稱爲有向圖。 若是圖中存在一個頂點,從該頂點出發通過若干條邊能夠回到該點,則這個圖是一個有向有環圖。這個環咱們暫且稱它爲閉環。網絡
除了引用計數內存管理,在工程應用中,不少地方都存在着閉環的影子:架構
判斷一個有向圖是否存在環,有多種算法。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.