天天看點

oc 記憶體管理初級

⼀、記憶體管理的⽅式

1、記憶體常見問題

(1)野指針異常:指針操作已經銷毀的對象

指針指向某對象,該對象釋放後,該指針即為野指針,對其操作造成野指針異常。

原因:過度釋放。

(2)記憶體溢出:超出記憶體上限

iOS給每個應⽤程式提供了⼀定的記憶體,⽤于程式的運⾏。iPhone 3GS記憶體 30M左右,iPhone 5S 記憶體80M左右。⼀旦超出記憶體上限,程式就會Crash。

2、記憶體管理的方式

(1)垃圾回收(gc)   |   OC支援 —  OS X開發 支援   |   iOS 不支援

程式員隻需要開辟記憶體空間,不需要⽤代碼顯式地釋放,系統來判斷哪些空間不再被使⽤,并回收這些記憶體空間,以便再 次配置設定。整個回收的過程不需要寫任何代碼,由系統⾃動完成垃圾回 收。Java開發中⼀直使⽤的就是垃圾回收技術。

(2)MRC (Manual Reference Count)  |  ⼈⼯引⽤計數   |   iOS支援

手動操作引用計數,手動調用控制引用計數的方法

記憶體的開辟和釋放都由程式代碼進⾏控制。

(3)ARC (Auto Reference Count)  |  ⾃動引⽤計數   |   iOS支援

自動操作引用計數,編譯器調用引用計數的方法

iOS 5.0的編譯器特性,它 允許⽤戶隻開辟空間,不⽤去釋放空間。它不是垃圾回收!它的本質 還是MRC,隻是編譯器幫程式員預設加了釋放的代碼。

⼆、記憶體管理機制

C語⾔中:使⽤malloc和free,進⾏堆記憶體的建立和釋放。堆記憶體隻 有正在使⽤和銷毀兩種狀态。 實際開發中,可能會遇到,兩個以上的指針使⽤同⼀塊記憶體。C語⾔ ⽆法記錄記憶體使⽤者的個數。

OC采⽤引⽤計數機制管理記憶體,當⼀個新的引⽤指向對象時,引⽤ 計數器就遞增,當去掉⼀個引⽤時,引⽤計數就遞減。當引⽤計數到 零時,該對象就将釋放占有的資源。

1、引⽤計數

    (1)引用計數   标記程式運作期間,對象被引用的次數

    (2)通過操作引用計數,控制對象是否被銷毀。

    (3)當引用計數應該減為0時,對象自動被銷毀,存儲空間被回收

2、操作引⽤計數的⽅法

alloc\ retain\ copy\ release\ autorelease

    (1)造成引用計數增加

     除了NSString的對象使用copy 其餘的對象類型都聲明成retain

        +alloc  目前對象 計數  0 -> 1

     Person * p = [[Person alloc] init];

     NSLog(@"%lu", p.retainCount);

        -retain  目前對象  計數 加1

          指針的引用

     新對象與原對象指向相同對象

     [pretain];

     NSLog(@"%lu", p.retainCount);

     Person * p3 = [p2 retain];

     NSLog(@"%lu,%lu,%lu", p.retainCount, p2.retainCount, p3.retainCount);

        -copy  原來的對象計數 不變   新的對象  0 -> 1

          拷貝

     詳情見四

    (2)造成引用計數減少

        release  目前對象  立即減1

     // 調用release,實作引用計數-1

     [p release];

     NSLog(@"%lu", p.retainCount);

     [p release];

     NSLog(@"%lu", p.retainCount);

     // 當引用計數應該減為0時,對象被銷毀,存儲空間被回收(系統完成)

     // person對象被銷毀(隻是标記删除,并不清空資料),不建議通過指針操作

     // 對象被銷毀後,列印引用計數顯示結果為1

     [p release];

     NSLog(@"%lu", p.retainCount);   // 應該減為0

        autorelease   目前對象  延遲減1  非立即

    [pe1 autorelease]; // 未來的某個時刻引用計數-1(管理其的自動釋放池release時)

        autoreleasepool的使⽤:

第一種寫法:将對象寫在pool的建立和release之間

             NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

        Person * p = [[Person alloc] init]; // 1

        [p retain]; // 2

        [p retain]; // 3

        NSLog(@"%lu", pool.retainCount);

        [p autorelease]; // 3

        [p autorelease]; // 3

        NSLog(@"%lu", pool.retainCount);

        // release銷毀自動釋放池(扔掉垃圾桶)

        [pool release];

        // drain清空自動釋放池(隻倒垃圾)

        //[pool drain];

第二種寫法:mrc支援兩種,arc隻支援第二種

        Person * pe = [[Person alloc] init];      //1

        [pe retain];     // 2

        NSLog(@"--- %lu", pe.retainCount);     

        @autoreleasepool {

            [pe autorelease];     // 2

            NSLog(@"--- %lu", pe.retainCount);

        }

        NSLog(@"--- %lu", pe.retainCount);     // 1

3、銷毀對象

    dealloc  引用計數将要減為0時,對象自動調用

         (1)繼承自NSObject,可以不實作,編譯器預設實作

         (2)如果實作dealloc方法

             - (void)dealloc

             {

                 代碼

                 [super dealloc];    

             }

QA:

dealloc和release到底執行了什麼操作?

三、記憶體管理的基本原則

引⽤計數的增加和減少相等,當引⽤計數降為0之後,不應該再使⽤這塊記憶體空間。

正常情況

     凡是使⽤了alloc、retain或者copy讓記憶體的引⽤計數增加了,就需 要使⽤release或者autorelease讓記憶體的引⽤計數減少。在⼀段代碼 内,增加和減少的次數要相等。

異常情況:

假設目前pe指向的對象的引用計數為1

如果不寫[pe release]; main函數運作結束後,局部變量pe被銷毀,沒有指針指向對象,造成記憶體洩漏

如果寫了不止一句[pe release];造成過度釋放。本質:野指針異常(pe 操作已釋放的對象)。

3、文法糖

利用文法糖建立的對象和類方法建立的對象相同,均由自動釋放池管理。

NSArray * arr = @[@"asfe",@"23",@"2"];

NSNumber *number = @123;

四、掌握copy的實作

自定義對象的拷貝要實作NSCopying協定和NSMutablecopying協定,但是在NSCopying協定中也能實作本質上的深拷貝是以隻需實作一個方法就可以。

1、深淺拷貝

深拷貝 拷貝的是對象,即建立了新的對象

淺拷貝  拷貝的是指針,即操作的是原來的對象,沒有建立新的對象

// 實作淺拷貝

- (id)copyWithZone:(NSZone *)zone

{

    return [self retain];

}

// 深拷貝

- (id)copyWithZone:(NSZone *)zone

{

//    Person * p = [[Person allocWithZone:zone] init];  // 以前的iOS版本需要這樣寫,如果需要向下相容可以寫這句代碼

    Person * p = [[Person alloc] init];

    p.name = self.name; // self表示調用copy方法的對象。即被拷貝的對象

    return p;

}

2、copy 和 mutableCopy

 對象調用copy方法得到的是不可變的對象

 對象調用mutableCopy得到的是可變的對象

 不可變對象建立不可變對象(NSString)為淺copy其餘全為深copy

NSZombieEnabled 環境變量

當設定NSZombieEnabled環境變量後,一個對象銷毀時會被轉化為_NSZombie,設定NSZombieEnabled後,當你向一個已經釋放的對象發送消息,這個對象就不會向之前那樣Crash或者産生一個難以了解的行為,而是放出一個錯誤消息,然後以一種可預測的可以産生debug斷點的方式消失, 是以我們就可以找到具體或者大概是哪個對象被錯誤的釋放了。 

注意:NSZombieEnabled環境變量需要每次建立工程時設定,且上傳之前必須關閉

*** -[__NSArray addObject:]:message sent to deallocated instance 0x6557370

Xcode打開僵屍對象:

個人總結:NSString 建立的字元串内容為ASCII,則在常量區建立常量字元串,否則,在堆區建立(待檢驗)

NSString * str1 = @"易荟雲";請問str1的retainCount是多少?

@""在常量區,str1的retainCount為最大值。

drain與release差別

在MRC下,兩者基本一樣,在GC環境下,release 是一個no-op(無效操 作),是以無論是不是gc都使用drain

iOS有沒有垃圾回收?autorelease 和垃圾回收制(gc)有什麼關系? 

沒有。autorelease隻是延遲釋放,gc是每隔一段時間詢問程式,看是否有無指針指向的對象,若有,就将它回收。他們兩者沒有什麼關系。 

版權聲明:本文為CSDN部落客「weixin_34072637」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/weixin_34072637/article/details/91729598

繼續閱讀