1. 程式人生 > >iOS記憶體管理

iOS記憶體管理

記憶體管理原則

  • 自己生成的物件,自己持有
  • 非自己生成的物件,自己也能持有
  • 不在需要自己持有的物件時釋放
  • 非自己持有的物件無法釋放

自己生成的物件,自己持有

使用 alloc/new/copy/mutableCopy 名稱開頭的方法名意味著自己生成的物件只能自己持有!

// 自己生成並持有物件
id obj = [[NSObject alloc] init];

id obj1 = [NSObject new];

使用NSObject類的alloc類方法就能自己生成並持有物件。指向生成並持有物件的指標被賦給變數obj。另外,使用new類方法也能生成並持有物件,兩中方式是完全一致的。

而copy方法則利用基於NSCopying方法約定,由各類實現的copyWithZone:

方法生成並持有物件的副本,雖說是物件副本,但同alloc、new方法一樣。mutableCopy也是如此。

非自己生成的物件,自己也能持有

// 取得非自己生成的物件,但並不持有
id obj = [NSMutableArray array];

// 自己持有物件
[obj retain];

原始碼中,NSMutableArray類物件被賦值給變數obj,但變數obj自己並不持有該物件,所以使用retain方法可以持有物件。

不再需要自己持有的物件時釋放

自己持有的物件,一旦不再需要,持有者有義務釋放該物件。

// 自己生成並持有物件
id obj = [[NSObject alloc] init];

// 釋放物件
[obj release];

如此,自己生成並持有的物件就通過release方法釋放了。需要注意的是,物件一旦釋放絕對不可訪問。

那麼,呼叫[NSMutableArray array]方法取得的物件存在,但自己不持有物件是如何實現的呢?

- (id)object
{
    // 自己持有物件
    id obj = [[NSObject alloc] init];

    // 自動釋放
    [obj autorelease];

    return obj;
}

上例中,使用了autorelease方法。用該方法,可以使取得的物件存在,但自己卻不持有物件。autorelease提供這樣的功能,使物件在超出指定的生存範圍時能夠自動並正確地釋放(呼叫release方法)。

呼叫release方法物件會立即釋放。但呼叫autorelease方法不會立即釋放,而是把物件註冊到autoreleasepool中,經過一段時間後,pool結束時讓裡面的物件自動呼叫release方法,這個後面會詳細說明!

無法釋放非自己持有的物件

對於用alloc/new/copy/mutableCopy方法生成並持有的物件,或是用retain方法持有的物件,由於持有者是自己,所以在不需要該物件的時候將其釋放。而由此以外所得到的物件絕對不能釋放,因為釋放了非自己所持有的物件就會造成崩潰!

example1:自己生成並持有的物件,在釋放完後再次釋放

// 自己生成並持有物件
    id obj = [[NSObject alloc] init];

    // 釋放物件
    [obj release];

    // 釋放之後再次釋放非自己持有的物件
    // 崩潰資訊:訪問一塊壞記憶體
    [obj release];

example2:在“取得的物件存在,但自己不持有物件”時釋放

// 取得非自己生成的物件,但並不持有
id obj = [NSMutableArray array];

[obj release];

記憶體管理的引用計數實現原理

蘋果對引用計數的實現是:採用引用計數表管理引用計數(下面是列舉的兩個好處)

  • 物件用記憶體塊的分配無需考慮記憶體塊頭部
  • 引用計數表各記錄中存有記憶體塊地址,可從各個記錄追溯都各個物件的記憶體塊

所以可知,第二條在除錯的時候有很重要的作用。即使出現故障導致物件佔用的記憶體塊損壞,但只要引用計數表沒有被損壞,就能夠確認各記憶體塊的位置。而且在利用工具檢測記憶體洩漏時,引用計數表的各記錄也有助於檢測各物件的持有者是否存在。

Autorelease

Autorelease的使用

autorelease從名字上來看,意思就是“自動釋放”,它類似於C語言中區域性變數的特性。當超出其作用域時,物件例項的release例項方法會被呼叫。

autorelease使用如下:

  • 生成並持有NSAutoreleasePool物件
  • 呼叫已分配物件的autorelease例項方法
  • 廢棄NSAutoreleasePool物件

而且對於所有呼叫過autorelease例項方法的物件,在廢棄NSAutoreleasePool物件時,都將呼叫release例項方法。

Autorelease的實現

在GNUstep的原始碼中,autorelease的實現為如下程式碼:

- (id)autorelease
{
    [NSAutoreleasePool addObject:self]; 
}

可以看出,autorelease例項方法的本質就是呼叫NSAutoreleasePool物件的addObject類方法。

當呼叫NSObject類的autorelease例項方法時,該物件會被追加到正在使用的NSAutoreleasePool物件中的數組裡.

所有權修飾符

Objective-C中為了處理物件,可將變數型別定義為id型別或各種物件型別。

  • 物件型別:指向NSObject這樣的Objective-C類的指標
  • id型別:用於隱藏物件型別的類名部分,相當於C語言中常用的void *

ARC有效時,id型別和物件型別同C語言其他型別不同,其型別上必須附加所有權修飾符,一共有四種:

  • __strong
  • __weak
  • __unsafe _unretained
  • __autoreleasing

__strong 修飾符

__strong修飾符是id型別和物件型別預設的所有權修飾符。

// 下面兩句程式碼效果是一樣的
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];

__strong修飾對物件的“強引用”。持有強引用的變數在超出其作用域時被廢棄,隨著強引用的實效,引用的物件會隨之釋放。

{
    // 自己生成並持有物件
    // 因為變數obj為強引用,所以自己持有物件
    id __strong obj = [[NSObject alloc] init];

}

/*
 *  因為變數obj超出其作用域,強引用實效。
 *  所以自動釋放自己持有的物件,物件的所有者不存在,因此廢棄該物件
 */

__strong修飾符的變數,不僅只在變數作用域中,在賦值上也能正確地管理其物件的所有者,而且Objective-C類成員變數,也可以在方法引數上,使用附有__strong修飾符的變數。

所以很明顯,__strong修飾符很符合“引用計數式記憶體管理的思考方式”。“自己生成的物件,自己持有”和“非自己生成的物件自己也能持有”只需通過對帶__strong修飾符的變數賦值便可達成。通過廢棄帶__strong修飾符的變數或者對變數賦值,就可以做到“不再需要自己持有的物件時釋放”。而由於不再鍵入release,所以“非自己持有的物件無法釋放”根本就不可能執行。

__weak 修飾

__strong是有一個致命的缺點,那就是容易出現兩個物件相互引用(迴圈引用容易發現記憶體洩漏)和一個物件自引用,而__weak就可以避免這種現象。

這裡寫圖片描述

{
    // 自己生成並持有物件
    id __strong obj0 = [[NSObject alloc] init];

    // obj1變數持有生成物件的弱引用
    id __weak obj1 = obj0;
}

/**
 *  因為obj0變數超出其作用域,強引用實效,所以自動釋放自己的持有的物件
 *  而物件的所有者不存在,所以廢棄該物件
 */

帶__weak修飾符的變數不持有物件,所以在超出其變數作用域的時候,物件即被釋放。

__weak修飾符還有另一個優點。在持有某物件的弱引用時,若該物件被廢棄,則此弱引用將自動失效,並且處於nil被賦值的狀態。

id __weak obj1 = nil;

{
    id __strong obj0 = [[NSObject alloc] init];

    obj1 = obj0;

    // 輸出obj1變數持有的弱引用的物件
    NSLog(@"A: %@", obj1);
}

/**
 *  因為obj0變數超出其作用域,強引用失效,所以自動釋放自己持有的物件
 *  物件無持有者,所以廢棄該物件
 *
 *  廢棄該物件的同時,持有該物件弱引用的obj1變數的弱引用失效,nil賦值給obj1
 */
NSLog(@"B: %@", obj1);

列印結果:
這裡寫圖片描述

因此,使用__weak可以避免迴圈引用,而且通過檢查附有__weak修飾符的變數是否為nil,可以判斷被賦值的物件是否已廢棄。

__unsafe _unretained 修飾符

ARC式的記憶體管理是編譯器的工作,但附有__unsafe _unretained修飾符的變數不屬於編譯器的記憶體管理物件。

__unsafe _unretained修飾符和__weak修飾符其實類似,在上面的程式碼中把修飾符改為__unsafe _unretained後,列印結果就為:

需要注意的是:賦值給附有__unsafe _unretained修飾符變數的物件在通過該變數使用時,如果沒有確保其確實存在,那麼應用程式就會崩潰

__autoreleasing 修飾符

ARC有效時,不能使用autorelease方法,也不能使用NSAutoreleasePool類。不過實際上,ARC有效時autorelease功能也是起作用的。

ARC有效時,@autoreleasePool塊代替了NSAutoreleasePool類,__autoreleasing修飾符代替了autorelease方法。 而且__autoreleasing修飾符和__strong一樣,一般都是非顯式的附加。

注意點:編譯器會自動檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是則自動將返回值的物件註冊到autoreleasePool中,而且根據命名規則,init方法返回值的物件不註冊到autoreleasePool中

在ARC有效時,[NSMutableArray array]方法的內部實現如下:

+ (id)array
{
    id obj = [[NSMutableArray alloc] init];

    return obj;
}

由於return使得物件變數超出其作用域,所以該強引用對應的自己持有的物件會被自動釋放,但該物件作為函式返回值,編譯器會自動將其註冊到autoreleasePool。

而且在訪問附有__weak修飾符的變數時,實際上必定要訪問註冊到autoreleasePool的物件。因為__weak修飾符持有物件的弱引用,而在訪問引用物件的過程中,該物件有可能會被廢棄。所以為了確保該物件存在,就會把要訪問的物件註冊到autoreleasePool中,那麼在@autoreleasePool塊結束之前都能讓其存在。

ARC規則

在ARC有效的情況下,必須遵守一定的規則。規則如下:

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 須遵守記憶體管理的方法命名規則
  • 不要顯式呼叫dealloc
  • 使用@autoreleasepool塊代替NSAutoreleasePool
  • 不能使用區域 (NSZone)
  • 物件型別變數不能作為C語言結構體的成員
  • 顯式轉換id和void *

相關推薦

iOS 記憶體管理研究

iPhone 作為一個移動裝置,其計算和記憶體資源通常是非常有限的,而許多使用者對應用的效能卻很敏感,卡頓、應用回到前臺丟失狀態、甚至 OOM 閃退,這就給了 iOS 工程師一個很大的挑戰。 網上的絕大多數關於 iOS 記憶體管理的文章,大多是圍繞 ARC/MRC、迴圈引用的原理或者是如何找尋記憶體洩漏來展

Objective-C runtime機制(5)——iOS 記憶體管理

概述 當我們建立一個物件時: SWHunter *hunter = [[SWHunter alloc] init]; 上面這行程式碼在棧上建立了hunter指標,並在堆上建立了一個SWHunter物件。目前,iOS並不支援在棧上建立物件。 iOS 記憶體分割槽 iOS

iOS記憶體管理的那些事兒-原理及實現

作者簡介 boyce,餓了麼物流團隊資深iOS開發。曾在格瓦拉等公司從事iOS相關研發工作。 注:本篇文章是《iOS記憶體管理的那些事兒》系列文章的第一部分。稍後我們會持續更新第二部分(開源監測記憶體洩漏的實現)和第三部分(如何利用開源工具做相關的APM),感興趣的童鞋可以關注我們專欄並獲取實

iOS 記憶體管理(補充)

  物件操作 OC中對應的方法 對應的 retain Count 變化 生成並持有物件 alloc/new/copy/mutableCopy等 +1 內容單元

iOS記憶體管理知識點梳理

1.iOS記憶體管理區域分為以下5個區域: 棧區,堆區,靜態區,常量區在記憶體分佈中以由高地址向低地址分佈的. (1).棧區(stack):它是有編譯器自動分配和管理的,存放區域性變數,函式的引數值.例如: - (NSString *)encodeBase64String:(NSString

iOS 進階—— iOS 記憶體管理 & Block

第一篇 iOS 記憶體管理 1 似乎每個人在學習 iOS 過程中都考慮過的問題 alloc retain release delloc 做了什麼? autoreleasepool 是怎樣實現的? __unsafe_unretained 是什麼? Block 是怎樣實現的 什麼

IOS記憶體管理知識總結(一)

    最近優化公司在優化app,總結幾個記憶體管理的知識點。    首先我們要清楚    1. “堆”和“棧” Objective-C的物件在記憶體中是以堆的方式分配空間的,並且堆記憶體是由你釋放的,就是releaseOC物件存放於堆裡面(堆記憶體要程式設計師手動回收)非

IOS記憶體管理--自動釋放池的實現原理

原文連結:http://www.cocoachina.com/ios/20150610/12093.html 記憶體管理一直是學習 Objective-C 的重點和難點之一,儘管現在已經是 ARC 時代了,但是瞭解 Objective-C 的記憶體管理機制仍然是十分必要的。其中,弄清楚 auto

iOS記憶體管理

記憶體管理原則 自己生成的物件,自己持有 非自己生成的物件,自己也能持有 不在需要自己持有的物件時釋放 非自己持有的物件無法釋放 自己生成的物件,自己持有 使用 alloc/new/copy/mutableCopy 名稱開頭的方法名意味著自己生成的

iOS記憶體管理記憶體洩露除錯的常用技巧

在往下看之前請下載例項MemoryProblems,我們將以這個工程展開如何檢查和解決記憶體問題。 懸掛指標問題 懸掛指標(Dangling Pointer)就是當指標指向的物件已經釋放或回收後,但沒有對指標做任何修改(一般來說,將它指向空指標),而是仍然指向原來已經回收的地址。如

iOS 進階—— iOS記憶體管理

1 似乎每個人在學習 iOS 過程中都考慮過的問題     alloc retain release delloc 做了什麼?     autoreleasepool 是怎樣實現的?     __unsafe_unretained 是什麼?     Block 是怎樣實現的     什麼時候會引起迴圈引用,

iOS 記憶體管理基本原則

我們知道objc中建立物件是存放在堆中的(基本資料型別除外,是由系統自己管理,並存放在棧中),系統不會自動釋放堆中的記憶體。如果建立完的物件存放在堆中後並使用完沒有得到及時的釋放,會佔用的記憶體。但是

2-2 iOS 記憶體管理,棧,堆,BSS段,資料段,程式碼段,野指標,殭屍物件

記憶體管理,拆開講就是對如何將資料儲存到記憶體中,如何釋放記憶體中的資料,什麼時候釋放。記憶體中的六大區域記憶體分為5個區域,分別指的是----->棧區/堆區/BSS段/資料段/程式碼段棧:儲存區

IOS記憶體管理,ARC,MRC,自動釋放池(基礎)

在IOS中記憶體管理幾乎是每個人必須知道的一個知識點。首先我們總結一下MRC,再通過MRC來認識ARC以及自動釋放池 1.MRC 1.1 淘汰的技術 1.2 引用計數(RC)是指alloc自動分配的一塊兒儲存空間,用於儲存持有該空間的指標個數 1.3 使

Objective-C高階程式設計:iOS與OS X多執行緒和記憶體管理

這篇文章主要給大家講解一下GCD的平時不太常用的API,以及文末會貼出GCD定時器的一個小例子。 需要學習的朋友可以通過網盤免費下載pdf版 (先點選普通下載-----再選擇普通使用者就能免費下載了)http://putpan.com/fs/cy1i1beebn7s0h4u9/ 1.G

[讀書筆記]iOS與OS X多執行緒和記憶體管理 [GCD部分]

3.2 GCD的API 蘋果對GCD的說明:開發者要做的只是定義想執行的任務並追加到適當的Dispatch Queue中。 “Dispatch Queue”是執行處理的等待佇列。通過dispatch_async函式等API,在Block

iOS面試之——記憶體管理

記憶體管理 1.什麼是ARC? ARC是automatic reference counting自動引用計數,在程式編譯時自動加入retain/release。在物件被建立時retain count+1,在物件被release時count-1,當count=0時,銷燬物件。程式中加入

理解iOS 和 macOS 的記憶體管理

在 iOS 和 macOS 應用的開發中,無論是使用 Objective-C 還是使用 swift 都是通過引用計數策略來進行記憶體管理的,但是在日常開發中80%以上的情況,我們不需要考慮記憶體問題,因為 Objective-C 2.0 引入的自動引用計數(ARC)技術為開發者們自動的完成了

iOS NSMutableArray 的記憶體管理原理

我一直想知道NSMutableArray內部如何運作。不要誤會我的意思,不可變陣列肯定會帶來巨大的好處。它們不僅是執行緒安全的,而且複製它們基本上是免費的。它並沒有改變它們非常沉悶的事實 - 它們的內容無法修改。我發現實際的記憶體操作細節令人著迷,這就是本文關注可變陣列的原因。 由於我或多或

理解 iOS 和 macOS 的記憶體管理

在 iOS 和 macOS 應用的開發中,無論是使用 Objective-C 還是使用 swift 都是通過引用計數策略來進行記憶體管理的,但是在日常開發中80%(這裡,我瞎說的,8020 原則嘛?)以上的情況,我們不需要考慮記憶體問題,因為 Objective