Swift基礎 — ARC 記憶體管理Weak 、Unowned

ChunYi LI
One Two Swift
Published in
7 min readJun 25, 2018

Swift作為一個現代的高階語言,對於記憶體管理也有一套的方法,
ARC — Automatic Reference Counting就如其名是自動化的管理記憶體,所以開發者不用多花心力在處理記憶體部分。

雖然說 swift 是採用自動化管理記憶體,但不是說開發者對記憶體就沒有責任,開發者沒有注意的話很容易造成 — Memory Leak (記憶體溢出)。

這代表開發者創造的物件無論使用與否,他都會佔用記憶體位置。

所以這時我們可以做的事就是做好物件跟物件之間的關聯。這些關聯會決定這個物件的生命期長短,也就代表可以在不使用這個物件時把它所佔的記憶體空間釋放出來。

物件生命期

deinit(類別解構方法) : 當物件死亡時會執行 deinit 方法裡的動作。

維持一個class物件的生命就是靠其 Reference count , Reference count是指這個物件所指到的property數量,在這個例子狗狗的 Reference count 就是 1,當物件死亡時,代表將沒有東西指到這些Property,所以系統會把這些Property所佔的空間釋放出來。

當狗狗Lucky被創造(初始化)時,記憶體會分配一個空間儲存他,當狗狗死亡時,記憶體就釋放其記憶體空間。

Reference Cycle

Reference Cycle是指物件是由其他物件組成,當被參考的被參考的物件死亡時,我們觀察記憶體實際上卻沒有釋放空間。

我創造了狗狗物件還有骨頭物件,而骨頭物件只有一個 property 就是一個Owner,所以此時狗狗物件就是骨頭物件的被參考物件。

當我把一個名為Lucky的狗狗設為nil時,卻發現狗狗物件的deinit方法卻沒有做任何事情,這代表了記憶體沒有釋放存取狗狗物件的空間,這就是所謂的Reference Cycle。

明明我已經讓物件死亡,但它卻還活在記憶體裡,這是一件非常沒有效率的事情,如果這樣的事情一直在你的程式裡存在,就會造成 Memory leak。

後來我出於好奇繼續檢查這個物件,我在上面那段程式加了兩行。

print(lucky?.name)
//print: nil
print(bone.owner.name)
//print: Lucky

奇怪!記憶體明明沒有釋放空間,但是我怎麼 print 不出來 Lucky 的名字?然而用骨頭的主人的名字卻可以print出名字,這真的是很弔詭的事情。

後來我看到有文章說記憶體裡面長這樣。

在創造lucky時,記憶體創造了一個空間存放一個名為 ” Lucky “ 的狗狗物件,然後指派給常數 lucky。

創造bone時將這個骨頭物件的owner指到我們前面創造的那個名為 ” Lucky “ 的狗狗物件上。

當我讓 lucky = nil 時,常數 lucky 不再指到 名為 ” Lucky “ 的狗狗物件所以print不出來,但bone.owner還是指著名為 ” Lucky “ 的狗狗物件所以可以print出來。如下圖

這時就可以解釋為何print不出 lucky.name,但是 bone.owner.name卻可以。

這就造成了明明我讓該物件死亡,但因為還是有東西參考該記憶體位置裡的東西所以記憶體不會釋放該空間的現象。

試想更怪的情況,當狗狗物件還有骨頭物件的property都互相參考對方,再讓這兩個物件死亡,此時會發生更神奇的事情。如下圖

當 lucky 還有 bone 為 nil 時,其記憶體位置將不再有外部參考到他們,但是由於內部還有其他地方指到自己,所以記憶體不會釋放。

但這時明明不會有人用到該記憶體空間裡面儲存的東西,但此記憶體空間一直都是被佔滿的狀態。程式內太多這種情況就會造成 Memory Leak。

Reference cycle對策 — weak

為了解決reference cycle這樣的窘境,我們可以使用在property 前面放個weak來解決。

weak var dog : Dog?

這時要注意被 weak 標記的 property 一定要是 optional

加上weak也就表示他所參考的記憶體位址裡的東西只是弱參考,也就是說有東西我就參考,沒有東西的話我就不參考。並且這個弱參考並不會增加reference count。程式碼如下。

Reference cycle對策 — unowned

標記unowned的效果跟weak非常像,都可以讓程式不會陷入reference cycle。但兩者之間還是有些差別。

weak 修飾 optional
unowned 修飾 non-optional

unowned所修飾的 property永遠不為 nil ,若將此 property 設為 nil 程式會編譯錯誤。

unowned在解決 reference cycle的方式跟weak 不一樣。

weak所參考的記憶體位置是弱參考,代表有沒有東西都無所謂,有就參考,沒有就不參考。

unowned期待他所參考的記憶體位置一定有東西的,一但參考的物件死亡,記憶體會迅速釋放記憶體空間,直到又有物件指派給他。程式碼範例如下。

這個範例跟前面的不太一樣,這邊是讓每個狗狗生成的時候都自帶一根骨頭,當狗狗物件死亡時,骨頭物件也會一並死亡。

觀察這邊的程式碼,你會發現這是一個互相參考的兩個物件,但狗狗所持有的骨頭是一個optional,骨頭物件的擁有人( owner)是一個 unowned 的 non-property 。

這就代表狗狗的出現不一定需要骨頭,但骨頭的出現一定需要狗狗。

這時如果我們這樣做

lucky?.bone = nil
//bone is deinitialized

我們拿走lucky的骨頭,記憶體會認為這個骨頭不在有人使用,會馬上釋放骨頭所佔的記憶體空間。

重點整理

Strong(沒有標注weak或unowned) : 只要有人參考到這個記憶體位置,記憶體就不會釋放位置,會造成Memory Leak。

weak : 所標記的 property 為 optional,被參考到的記憶體位置裡面有沒有東西都無所謂,不會增加reference count

unowned : 所標記的 property 為 non-optional, 期望所參考到的記憶體位置裡面一定有東西,如果沒有東西的話記憶體會直接釋放unowned所標記的物件的記憶體空間。

寫這篇真的花了我非常久的時間,就為了要真的釐清這篇的觀念。

如果我有哪裡寫的不對的話請留言告知我,反之覺得我寫得不錯的話請給我一點掌聲鼓勵一下。

小潘潘我知道你有在看,拍個手吧。

--

--

ChunYi LI
One Two Swift

Hi this is Chunyi-Li from Taiwan, a junior iOS deveoper