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

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

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

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

int a = 2;

void foo() {}

int main() {
    char str1[20] = "Gello, world!";
    char *str2 = "Gello, world!";// 只讀數(shù)據(jù)段,無(wú)法更改,內(nèi)存使用錯(cuò)誤
    char *str3 = (char *)malloc(1000);// 申請(qǐng)空間
    //str[0] = 'H';//只讀數(shù)據(jù)段無(wú)法更改 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í)候希望通過(guò)函數(shù)調(diào)用修改傳入的參數(shù)
// 那就不能只傳參數(shù)的值 而要傳參數(shù)的地址
// 如果傳入的參數(shù)本身就是地址 那么就要使用指向指針的指針
// 指針的第一個(gè)用途就是實(shí)現(xiàn)跨棧的操作
void get_memory2(char **p, int capacity) {
    // 指針的第二個(gè)用途就是申請(qǐng)堆空間
    *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;
    // 未申請(qǐng)到堆空間,只是為形參申請(qǐng)了100字節(jié)的空間
    // 為指針申請(qǐng)空間,需要傳指針的地址,用二重指針(指針的指針)
    //get_memory(str, 100);
    get_memory2(&str, 100);
    if (str)// 判斷是否申請(qǐng)到空間
    {
        strcpy(str, "Hello, world!");
        printf("%s\n", str);
        free(str);// 堆空間不會(huì)隨著棧的消失而消失,需要手動(dòng)釋放
        str = NULL;
    }
    return 0;
}
#include <stdio.h>

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

void main() {
    char *str = get_memory();
    if (str != NULL) {
        printf("%s\n", str);
        // 沒(méi)有申請(qǐng)空間(malloc), 不能釋放堆空間
        // free操作跟malloc操作是成對(duì)出現(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);
    // 申請(qǐng)內(nèi)存以后一定要先判斷再使用
    if (str) {
        bar(str);// 調(diào)用完后若有其他函數(shù)調(diào)用str,提前釋放了str,會(huì)出錯(cuò)
        //foo(str);// 重復(fù)釋放
    }
    return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

??特殊情況:

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

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

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

??ARC自動(dòng)釋放池的代碼示例

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 野指針(釋放了仍然還在使用)和內(nèi)存泄露(未正確的釋放)
        // 1、創(chuàng)建了一個(gè)學(xué)生對(duì)象(堆上)
        // 2、創(chuàng)建了一個(gè)指針(棧上)
        // 3、指針指向了學(xué)生能夠?qū)ο?指針中存儲(chǔ)了學(xué)生對(duì)象的地址)
        __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方法,但是覺(jué)對(duì)不見而已調(diào)用父類的dealloc方法
//在對(duì)象將要銷毀的時(shí)候會(huì)自動(dòng)調(diào)用dealloc
- (void) dealloc {
    NSLog(@"學(xué)生被銷毀!!");
}

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

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

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

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

    __autoreleasing:只用來(lái)修飾需要被傳入地址的指針。

  2. 屬性修飾符

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容