Objective-C:內(nèi)存管理

內(nèi)存管理的問題#

??先看看下面的幾段代碼,重溫一下使用內(nèi)存常見的問題。

#include <stdio.h>
#include <stdlib.h>

int a = 2;

void foo() {}

int main() {
    char str1[20] = "Gello, world!";
    char *str2 = "Gello, world!";// 只讀數(shù)據(jù)段,無法更改,內(nèi)存使用錯誤
    char *str3 = (char *)malloc(1000);// 申請空間
    //str[0] = 'H';//只讀數(shù)據(jù)段無法更改 bos error
    str1[0] = 'H';
    printf("棧(stack):str1 = %p\n", str1);
    printf("堆(head):str3 = %p\n", str3);
    printf("數(shù)據(jù)段:a = %p\n", &a);
    printf("只讀數(shù)據(jù)段:str2 = %p\n", str2);
    printf("代碼段:str2 = %p\n", foo);
    free(str3);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void get_memory(char *p, int capacity) {
    p = malloc(sizeof *p * capacity);
}

// 任何時候希望通過函數(shù)調(diào)用修改傳入的參數(shù)
// 那就不能只傳參數(shù)的值 而要傳參數(shù)的地址
// 如果傳入的參數(shù)本身就是地址 那么就要使用指向指針的指針
// 指針的第一個用途就是實(shí)現(xiàn)跨棧的操作
void get_memory2(char **p, int capacity) {
    // 指針的第二個用途就是申請堆空間
    *p = malloc(sizeof **p * capacity);
}

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int a = 5, b = 10;
    printf("a = %d, b = %d\n", a, b);
    swap(&a, &b);// 交換 a 和 b 的值
    printf("a = %d, b = %d\n", a, b);
    char *str = NULL;
    // 未申請到堆空間,只是為形參申請了100字節(jié)的空間
    // 為指針申請空間,需要傳指針的地址,用二重指針(指針的指針)
    //get_memory(str, 100);
    get_memory2(&str, 100);
    if (str)// 判斷是否申請到空間
    {
        strcpy(str, "Hello, world!");
        printf("%s\n", str);
        free(str);// 堆空間不會隨著棧的消失而消失,需要手動釋放
        str = NULL;
    }
    return 0;
}
#include <stdio.h>

char *get_memory() {
    // c中數(shù)組的數(shù)據(jù)放在棧空間中
    // str是一個局部變量,調(diào)用結(jié)束后會自動釋放
    // 一個函數(shù)可以返回??臻g的數(shù)據(jù)但不能返回??臻g的地址
    char str[] = "hello, world";
    char *str2 = "hello, world";// 放在只讀數(shù)據(jù)段中 
    // return str;// 無法返回??臻g的地址
    return str2;
}

void main() {
    char *str = get_memory();
    if (str != NULL) {
        printf("%s\n", str);
        // 沒有申請空間(malloc), 不能釋放堆空間
        // free操作跟malloc操作是成對出現(xiàn)的
        //free(str);
    }
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *get_memory(int capacity) {
    char *str = malloc(sizeof *str * capacity);
    return str;
}

void foo(char *str) {
    // ...
    free(str);
    str = NULL;
}

void bar(char *str) {
    strcpy(str, "Hello, world!");
    printf("%s\n", str);
    // 用完內(nèi)存一定需要釋放, 否則有內(nèi)存泄露的風(fēng)險(xiǎn)
    free(str);
    str = NULL; // important
}

int main() {
    char *str = get_memory(100);
    // 申請內(nèi)存以后一定要先判斷再使用
    if (str) {
        bar(str);// 調(diào)用完后若有其他函數(shù)調(diào)用str,提前釋放了str,會出錯
        //foo(str);// 重復(fù)釋放
    }
    return 0;
}

??C語言中內(nèi)存操作常見錯誤:

  1. 內(nèi)存分配未成功就開始使用內(nèi)存。

  2. 內(nèi)存分配雖然成功但尚未初始化就使用。

  3. 內(nèi)存分配成功且已經(jīng)初始化但訪問越界。

  4. 使用realloc()函數(shù)不使用備用指針。

  5. 內(nèi)存泄露(申請了堆空間,但在使用結(jié)束后忘記釋放)。

  6. 提前釋放(釋放了內(nèi)存卻仍然在使用的空間,導(dǎo)致數(shù)據(jù)不安全)。

  7. 重復(fù)釋放(釋放一個已經(jīng)釋放過的空間,導(dǎo)致程序崩潰)。

??上述問題中的第5項(xiàng)和第6項(xiàng)在實(shí)際開發(fā)中,尤其是遇到模塊化的團(tuán)隊(duì)開發(fā)或者程序中使用多線程的時候,顯得尤為難以處理。為了解決上述問題,在Objective-C中引入了引用計(jì)數(shù)的概念。Objective-C中每個類都是NSObject子類,因此每個對象都有一個內(nèi)置的計(jì)數(shù)器,這個計(jì)數(shù)器稱為引用計(jì)數(shù)(Reference Count),也稱保留計(jì)數(shù)(Retain Count)。所謂Objective-C的內(nèi)存管理,就是要維護(hù)引用計(jì)數(shù)器正確+1和-1,當(dāng)引用計(jì)數(shù)器為0時,對象正確釋放。在Objective-C中,每個對象就如同一個QQ討論組,當(dāng)有人創(chuàng)建討論組時,討論組人數(shù)為1(對象創(chuàng)建);每有一個人加入討論組,該討論組的人數(shù)+1(使用retain增加引用計(jì)數(shù)),每有一個人離開討論組,該討論組的人數(shù)-1(使用release減少引用計(jì)數(shù));如果討論組的人數(shù)為0,則自動解散。

??和內(nèi)存管理相關(guān)的方法:

  • retain:增加對象的引用計(jì)數(shù)。

  • release:減少對象的引用計(jì)數(shù)。

  • autorelease:在自動釋放池塊結(jié)束時減少對象的引用計(jì)數(shù)。

  • retainCount:引用計(jì)數(shù)的數(shù)量。

  • 將整個項(xiàng)目都改成手動內(nèi)存管理
    操作:(選擇no)

整個項(xiàng)目改為手動內(nèi)存管理.png
    • 將項(xiàng)目中的某個文件改成手動內(nèi)存管理
      操作:選擇需要改為手動管理的m文件添加 -fno-objc-arc
某個文件改成手動內(nèi)存管理.png

如何有效管理內(nèi)存##

??C語言中內(nèi)存操作要牢記以下幾點(diǎn):

  1. 用malloc()/realloc()/calloc()申請內(nèi)存后,應(yīng)理解檢查是否為NULL。

  2. 不要忘記為數(shù)組或動態(tài)申請的內(nèi)存賦值,防止未初始化的內(nèi)存作為運(yùn)算的右值。

  3. 內(nèi)存操作要小心邊界,防止操作越界。

  4. 分配內(nèi)存和釋放內(nèi)存的操作必須配對,防止內(nèi)存泄露。

  5. 用free()函數(shù)釋放內(nèi)存后將指針賦值為NULL,防止產(chǎn)生野指針。

??Objective-C中使用內(nèi)存的原則基本上是:

  1. 分配內(nèi)存的操作要和釋放內(nèi)存的操作成對出現(xiàn)。

  2. 誰分配了內(nèi)存,誰就要負(fù)責(zé)回收此內(nèi)存。

??特殊情況:

  1. 成員變量是對象指針,應(yīng)在析構(gòu)方法(dealloc)中釋放。

  2. 如果發(fā)生指針的轉(zhuǎn)移,應(yīng)釋放舊對象,retain新對象。

  3. 從對象持有者(如NSArray、NSDictionary等)中取出對象的指針,如需長期使用,需要retain。

??ARC自動釋放池的代碼示例

#import <Foundation/Foundation.h>
#import "YHStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 野指針(釋放了仍然還在使用)和內(nèi)存泄露(未正確的釋放)
        // 1、創(chuàng)建了一個學(xué)生對象(堆上)
        // 2、創(chuàng)建了一個指針(棧上)
        // 3、指針指向了學(xué)生能夠?qū)ο?指針中存儲了學(xué)生對象的地址)
        __weak YHStudent *stu = [[YHStudent alloc] init];// 弱指針,一初始化就銷毀 ,stu直接被銷毀
        __strong YHStudent *stu2 = [[YHStudent alloc] init];// 強(qiáng)指針
        YHStudent *student = [[YHStudent alloc] init];// 強(qiáng)指針

        stu2 = nil;//若賦值為空,則在這被釋放,stu2在這里被銷毀
    }// 在這兒被釋放(由于出了強(qiáng)指針(student)的作用域)
    return 0;
}
#import <Foundation/Foundation.h>

@interface YHStudent : NSObject

@property (nonatomic, copy) NSString *name;

@end
#import "YHStudent.h"

@implementation YHStudent

//ARC中可以重寫dealloc方法,但是覺對不見而已調(diào)用父類的dealloc方法
//在對象將要銷毀的時候會自動調(diào)用dealloc
- (void) dealloc {
    NSLog(@"學(xué)生被銷毀!!");
}

@end
  1. 四個關(guān)鍵字的使用

    __strong(強(qiáng)引用):缺省屬性,其修飾的對象指針,指向哪個對象,會對該對象retain,離開哪個對象,會對該對象release。

    __weak(弱引用):其修飾的對象指針,指向任何對象都不會retain。這樣的指針指向的對象隨時可能消失。如果對象消失了,這個指針會自動變成nil。

    __unsafe_unretained:其修飾的對象指針,指向任何對象都不retain。當(dāng)指向的對象消失,該指針不會變成nil,仍然指向已經(jīng)釋放的對象。

    __autoreleasing:只用來修飾需要被傳入地址的指針。

  2. 屬性修飾符

    • copy:控制@property實(shí)現(xiàn)的set方法,會先創(chuàng)建一個新的對象,將參數(shù)的值傳給新的對象,最后將新的對象賦值給成員變量.常用來修飾字符串、block、數(shù)組、字典、NSData;
    • strong:控制@property實(shí)現(xiàn)符合內(nèi)存管理的set方法,引用計(jì)數(shù)加1;修飾一般的對象(retain的替代品)
    • weak:控制@property實(shí)現(xiàn)一般的set方法(直接賦值),修飾對象用來避免循環(huán)引用(最常用的是delegate)
    • assign:控制@property實(shí)現(xiàn)一般的set方法(直接賦值);常用來修飾基本數(shù)據(jù)類型(int、float、char、結(jié)構(gòu)體、枚舉、聯(lián)合體)
    • retain:在MRC中相當(dāng)于strong(實(shí)現(xiàn)的set方法就是舊值release、新值retain)。
  3. 牢記在ARC有效時retain/release/autorelease/retainCount都不能用。

  4. 不能顯式調(diào)用dealloc析構(gòu)器,析構(gòu)器中可以將成員變量中的指針賦值為nil。

  5. 用@autoreleasepool{}替代NSAutoreleasePool對象的創(chuàng)建。

  6. 在ARC有效時id和void *不再等同,需要用__bridge轉(zhuǎn)換。

  7. 不要在C的結(jié)構(gòu)體中聲明對象指針,否則無法進(jìn)行內(nèi)存管理。

管理內(nèi)存的一些常見問題##

  • 內(nèi)存管理的作用:

    • 解決內(nèi)存泄露和野指針操作
  • 為什么要內(nèi)存管理,我們要注意的問題是什么?

    • 解決內(nèi)存泄露和野指針操作
  • 什么是黃金法則?

    • 內(nèi)存管理原則:誰創(chuàng)建誰釋放,在哪兒創(chuàng)建在哪兒釋放
  • @property參數(shù)(retain)要注意的問題:避免循環(huán)引用

  • 什么時候autorelease?與release的區(qū)別

    • 對象需要延時銷毀的時候使用autorelease
    • autorelease是將對象添加到自動釋放池中(延時對象的銷毀),release將對象的引用計(jì)數(shù)器減1
  • 什么是自動釋放池

    • 注意:autorelease和autoreleasepool是成對出現(xiàn)的
    • autoreleasepool的原理:當(dāng)autoreleasepool銷毀的時候,會將自動釋放池中所有的對象調(diào)用一次release方法
    • autorelease的作用:將對象放入自動釋放池中(并不是寫在自動釋放池的大括號中的對象就是在自動釋放池中的對象)
  • 一個工程中能有一個自動釋放池?

    • 錯,可以NSAutoreleasePool或者@Autoreleasepool{}去創(chuàng)建多個自動釋放池
  • 在手動內(nèi)存管理中,盡量都使用autorelease?

    • 錯,對象調(diào)用autorelease會延遲對象的銷毀,如果所有的對象都延遲銷毀的話,相當(dāng)于沒有做內(nèi)存管理
  • 對內(nèi)存管理的理解?(原理)重點(diǎn)

    • 手動(MRC):1、在創(chuàng)建一個對象的時候系統(tǒng)會自動創(chuàng)建這個對象的引用計(jì)數(shù),并且賦值為1;2、當(dāng)引用計(jì)數(shù)為0的時候,對象會去調(diào)用dealloc方法,來銷毀對象;3、對象調(diào)用release方法會讓引用計(jì)數(shù)減1,調(diào)用retain方法讓對象的引用計(jì)數(shù)加1。

    • 自動(ARC):在ARC中管理內(nèi)存的實(shí)質(zhì)還是通過引用器去管理的,但是程序員不再去關(guān)心引用計(jì)數(shù)的值。在ARC環(huán)境下,系統(tǒng)會在程序編譯的時候會自動在合適的地方添加retain、release或者autorelease。

    • 當(dāng)有強(qiáng)指針指向?qū)ο蟮臅r候,對象不銷毀;弱指針不影響對象的銷毀;指針默認(rèn)都是強(qiáng)指針

    • __weak 使用這個關(guān)鍵字修飾的指針是弱指針;__strong 使用這個關(guān)鍵字修飾的指針是強(qiáng)指針(默認(rèn)值);

  • 手動內(nèi)存管理的原則?

    • 程序中如果出現(xiàn)alloc、retain、new必須配對出現(xiàn)一個release或者autorelease(誰創(chuàng)建誰釋放,在哪兒創(chuàng)建在哪兒釋放)
  • 3、 autoreleasepool的原理和autorelease的作用

    • autoreleasepool的原理:當(dāng)autoreleasepool銷毀的時候,會將自動釋放池中所有的對象調(diào)用一次release方法
    • autorelease的作用:將對象放入自動釋放池中(并不是寫在自動釋放池的大括號中的對象就是在自動釋放池中的對象)
  • 4、 MRC中符合內(nèi)存管理setter函數(shù) 的書寫(舊值release,新值retain,然后賦值)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容