Objective C 記憶體管理(上)學習筆記

半紙淵發表於2017-12-14

一.開篇之初

  1. 記憶體管理解決的問題就是:
    1)防止野指標的生成
    (野指標:指向變數的指標還存在,但是所指向的記憶體已經被釋放,此時的指標就變成了野指標 --- 沒有指向 內容 的指標)
    2)防止出現記憶體洩漏
    (記憶體洩漏:指向記憶體空間的指標已經被釋放,但是該指標指向的記憶體空間還在記憶體中存在(被佔用) -- 沒有 “ 地址 ” 的記憶體)
    3)合理使用記憶體,防止有限記憶體的大量消耗

  2. Objective-C的記憶體管理有三種,其中iOS中能用的,就是MRC(手動引用計數)和ARC(自動引用計數,官方推薦使用);而另外一個垃圾回收機制,只能用在OS X系統中。

  3. 記憶體管理管理的範圍是,Objective-C
    物件(基本資料型別由系統自動管理)。

  4. MRC是基於引用計數的記憶體管理,是否釋放記憶體取決於引用計數是否為0;但注意,真正要研究並不是引用計數,而是物件是否被持有的問題。

  5. ARC是基於自動引用計數的記憶體管理,是否釋放記憶體取決於物件是否還有強引用指向;真正研究的是,物件的所有權問題。(所有權的概念是ARC中引入的)


二.記憶體管理的思考方式

>引自:《Objective-C高階程式設計 iOS與OS X多執行緒和記憶體管理》
  • 自己生成的物件,自己所持有
  • 非自己生成的物件,自己也能持有
  • 自己持有的物件不再需要時釋放
  • 非自己持有的物件無法釋放

換個方式來解讀:

- 自己申請的記憶體,自己所掌管(擁有) - 不是自己申請的記憶體,自己也可以掌管(擁有) - 自己掌管(擁有)的記憶體不再需要時就釋放(free) - 不是自己掌管(擁有)的記憶體,無法釋放(free)

三.MRC(Manual Reference Counting)記憶體管理

--> 小小結 <--

- MRC模式下物件什麼時候被銷燬?引用計數值為0的時候。 - MRC使用的管理記憶體的基本方法和屬性: - 四個方法 --> retain/release/dealloc/autorelease/ - 一個屬性 --> retainCount(記錄引用計數值) <----均定義在 NSObject.h 中---->
//define OBJC_ARC_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
複製程式碼
  • 誰retain,誰release
  • retain既是把retainCount值加 1; release既是把retainCount值減 1
  • dealloc只有在 retainCount = 0 的時候,由系統自動呼叫
  • autorelease是把物件加進自動釋放池中,由系統自動為池中的物件傳送release訊息

  • 問題 1:什麼是引用計數(Reference Counting)?
    • 引用計數 ? 這裡的“計數”表明必然會有一個東西(變數)來記錄引用的變化,而在OC裡這個變數就是retainCount;那麼還有一個問題就是通過什麼方式來操作這個變數,OC裡就是retain(引用次數加 1),release(引用計數減 1 )方法。
    • 引用計數:就是分配的 記憶體區塊 多少個 OC物件所持有(掌管;保持且擁有),間接表示就是retainCount值的大小。

注:物件,指人可以識別的東西,具備屬性、收發資訊、處理資訊;而從系統的角度看,操作物件就是操作一塊記憶體。(可能不是很準確......)

  • 問題 2 :引用計數如何管理OC物件?

    • 首先明確,引用計數的變化是被持有者的變化。
    • 那麼問題就是怎樣持有物件(持有記憶體):
      • 持有:就是可以訪問記憶體,且可以進行讀寫操作,而一般是通過記憶體的首地址進行記憶體的訪問,就是指標訪問。
      • 而OC中一般用來分配記憶體的的函式是alloc/new/copy/mutablecopy(當然還有clloc...等等),它們返回的都是指標,就是使用他們來生成物件並持有物件的
  • 問題 3:持有?釋放?銷燬?物件... , 請看下錶:

OC操作方法 物件的操作 retainCount
alloc/new/copy/mutablecopy等 生成並持有物件 1?
retain 持有物件 +1
release 釋放物件 -1
dealloc 銷燬物件 此時該值沒有意義
autorelease 在自動釋放池結束時,為裡面的物件傳送一條release訊息 (all object) -1

上面涉及的方法定義如下(NSOject.h):

//define OBJC_SWIFT_UNAVAILABLE(_msg) __attribute__((availability(swift, unavailable, message=_msg)))
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

- (id)copy;
- (id)mutableCopy;
複製程式碼
  • 問題 4:什麼是自動釋放池?
    • 自動釋放池:在自動釋放池結束時,系統自動為裡面的物件傳送一條release訊息(when the pool itself is drained)
    • 要使用自動釋放池就要使用NSAutoreleasePool物件
      NSAutoreleasePool
    • NSAutoreleasePool它的方法
      Objective C 記憶體管理(上)學習筆記
    • 使用方法:
      • 建立一個NSAutoreleasePool物件 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      • 新增要釋放的物件進NSAutoreleasePool物件中 id obj = [NSString alloc] initWithstring:@"objective-c pool"]; [obj autorelease];[pool addObject:obj]; --- 1
      • 釋放NSAutoreleasePool物件 [pool drain];等同於[pool release]; --- 2

注意: 1 --> 建議使用autorelease方法,因為後面的方法會導致同一個物件被多次加入自動釋放池中。

addObject:方法
2 --> 雖然兩個方法效果等同,但還是建議使用自動釋放池專門的drain方法。
drain方法

  • 補充autorelease方法

    autorelease方法

  • 問題 5:MRC下如何防止野指標訪問?

    • 野指標訪問:指向的記憶體空間已經被釋放了,但是指標還指向著已經被釋放的記憶體,此時的指標就是野指標。
    • 訪問了不存在的記憶體,當然會引起程式崩潰
    • 修改Xcode工程為MRC模式
      Objective C 記憶體管理(上)學習筆記
    • 開啟對應targets的殭屍物件檢測,詳細步驟如下:
      選擇Edit Scheme
      勾選殭屍物件檢測
    • 情況 1:過快釋放了物件(不要理retaiinCount把注意力放在物件被持有的個數上)
      • retainCount的補充:
        只能用在除錯階段,值是不可靠的
      • 程式程式碼和執行結果
        tesh.m
        main.m
        指向異常的程式碼
  • 問題 6:MRC下如何防止記憶體洩漏?

    • 自己生成的物件,自己所持有
    • 非自己生成的物件,自己也能持有
    • 自己持有的物件不再需要時釋放
    • 非自己持有的物件無法釋放 補充:
      持有物件
      執行結果
      疑問:mArrayCopy的retainCount是2 ?被持有者有兩個?
      再來一次release
      從這裡就可以證明了,cope出來的新物件只是被mArrayCopy自己所持有而已,所以當release一次的時候物件已經被釋放了,如果再release就是野指標訪問了(注:直接看持有者有多少)。 程式碼:
    /**
     *  alloc就是分配記憶體的意思,返回了一個指向記憶體首地址的指標
     */
    NSMutableArray *mArrayAlloc = [[NSMutableArray alloc] init];  // mArrayAlloc 持有物件
    /**
     *  new 就相當於alloc+init,但是new有可能會返回同一個物件,所以並不建議使用
     */
    NSMutableArray *mArrayNew = [NSMutableArray new];   // mArrayNew 持有物件
    
    /**
     *  copy是一個例項方法,具體如下:
     *  - (id)copy
     *  Returns the object returned by copyWithZone:.
     *  - (id)copyWithZone:(NSZone *)zone
     *  Returns a new instance that’s a copy of the receiver.
     *  ---new instance 就表明了建立了一個新的記憶體,並返回首地址(id 相當於 void *)
     */
    NSMutableArray *mArrayCopy = [mArrayAlloc copy];   //mArrayCopy 持有了物件
    
    /**
     *  Returns the object returned by mutableCopyWithZone:.
     *  - (id)mutableCopy
     *  - (id)mutableCopyWithZone:(NSZone *)zone
     *  Returns a new instance that’s a mutable copy of the receiver.
     *  ---new instance 就表明了建立了一個新的記憶體,並返回首地址(id 相當於 void *)
     */
    NSMutableArray *mArrayMutablecopy = [mArrayNew mutableCopy];//mArrayMutablecopy 持有了物件
複製程式碼
  • 持有物件
    原始碼
    執行結果
    明顯的野指標訪問了
  • 使用copy來獨立管理記憶體
    使用copy原始碼
    內容沒有改變
  • 如果記憶體還在使用的話,當然不要把物件賦值為nil
  • 物件之間相互持有的情況
    • 程式程式碼
      Apple.h
      Apple.m
      Girl.h
      Girl.m
      main.m
      如果要達到目的,apple讓girl也持有,就要在girl得到apple的時候持有一下,而可以做持有操作的是retain,來看看:
      記憶體洩漏
      我們知道物件在最後銷燬的時候是呼叫了dealloc方法的,那麼girl既然持有了apple那麼在銷燬自己的時候是不是應該把自己持有的東西給交出來(釋放掉),已死的物件不可能持有東西了吧,所以在girl的dealloc方法中加上apple釋放的程式碼:
      Objective C 記憶體管理(上)學習筆記
      雖然上面的方法是可以的,但是有問題,問題如下:
      retain
      apple再持有一下[[Apple alloc] init],再給girl,直接翻譯都是問題,而且從封裝性來看,girl要持有apple應該是自己去持有,也就是要自己進行retain,而不是要apple先retain再給girl,程式碼優化:
      retain去掉
      set方法中進行retain
      還有,如果我們從現實生活中考慮問題(物件導向是現實世界的抽象),girl會不會只要一次apple呢?多要幾個~~
      Objective C 記憶體管理(上)學習筆記
      為了防止記憶體洩漏,我得這麼幹,估計你看到這就想呵呵了:
      Objective C 記憶體管理(上)學習筆記
      正常釋放
      再次優化程式碼,目的是隻要girl再次要一個新的apple就給它持有,如果是拿原來的apple當然不再次持有咯:
      做if判斷
      正常釋放
      程式碼修改成下面這樣就是真正的,girl直接的持有一個新的apple(新的記憶體空間)了,不過結果是一樣的,正常釋放:
      Objective C 記憶體管理(上)學習筆記

ARC( Automatic Reference Counting)記憶體管理

- 請期待下一篇......

相關文章