Objective-C 內(nèi)存管理(上)學(xué)習(xí)筆記


一.開篇之初

  1. 內(nèi)存管理解決的問題就是:

    1)防止野指針的生成
    (野指針:指向變量的指針還存在,但是所指向的內(nèi)存已經(jīng)被釋放,此時的指針就變成了野指針 -- 沒有指向 “ 內(nèi)容 ” 的指針)

    2)防止出現(xiàn)內(nèi)存泄漏
    (內(nèi)存泄漏:指向內(nèi)存空間的指針已經(jīng)被釋放,但是該指針指向的內(nèi)存空間還在內(nèi)存中存在(被占用) -- 沒有 “ 地址 ” 的內(nèi)存)

    3)合理使用內(nèi)存,防止有限內(nèi)存的大量消耗

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

  3. 內(nèi)存管理管理的范圍是,Objective-C 對象(基本數(shù)據(jù)類型由系統(tǒng)自動管理)。

  4. MRC是基于引用計數(shù)的內(nèi)存管理,是否釋放內(nèi)存取決于引用計數(shù)是否為0;但注意,真正要研究并不是引用計數(shù),而是對象是否被持有的問題。

  5. ARC是基于自動引用計數(shù)的內(nèi)存管理,是否釋放內(nèi)存取決于對象是否還有強引用指向;真正研究的是,對象的所有權(quán)問題。(所有權(quán)的概念是ARC中引入的)


二.內(nèi)存管理的思考方式

引自:《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》

  • 自己生成的對象,自己所持有
  • 非自己生成的對象,自己也能持有
  • 自己持有的對象不再需要時釋放
  • 非自己持有的對象無法釋放

換個方式來解讀:

  • 自己申請的內(nèi)存,自己所掌管(擁有)
  • 不是自己申請的內(nèi)存,自己也可以掌管(擁有)
  • 自己掌管(擁有)的內(nèi)存不再需要時就釋放(free)
  • 不是自己掌管(擁有)的內(nèi)存,無法釋放(free)

三.MRC(Manual Reference Counting)內(nèi)存管理

--> 小小結(jié) <--

  • MRC模式下對象什么時候被銷毀?引用計數(shù)值為0的時候。

  • MRC使用的管理內(nèi)存的基本方法和屬性:

    • 四個方法 --> retain/release/dealloc/autorelease/
    • 一個屬性 --> retainCount(記錄引用計數(shù)值)
      <strong><----均定義在 NSObject.h 中----></strong>
//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 的時候,由系統(tǒng)自動調(diào)用
  • autorelease是把對象加進自動釋放池中,由系統(tǒng)自動為池中的對象發(fā)送release消息

  • 問題 1:什么是引用計數(shù)(Reference Counting)?

  • 引用計數(shù) ?

這里的“計數(shù)”表明必然會有一個東西(變量)來記錄引用的變化,而在OC里這個變量就是retainCount;那么還有一個問題就是通過什么方式來操作這個變量,OC里就是retain(引用次數(shù)加 1),release(引用計數(shù)減 1 )方法。

  • 引用計數(shù):就是分配的<strong> 內(nèi)存區(qū)塊 </strong>被<strong> 多少個 </strong>OC對象所持有(掌管;保持且擁有),間接表示就是retainCount值的大小。

注:對象,指人可以識別的東西,具備屬性、收發(fā)信息、處理信息;而從系統(tǒng)的角度看,操作對象就是操作一塊內(nèi)存。(可能不是很準(zhǔn)確......)

  • 問題 2 :引用計數(shù)如何管理OC對象?

  • 首先明確,引用計數(shù)的變化是被持有者的變化。

  • 那么問題就是怎樣持有對象(持有內(nèi)存)?

  • 持有:就是可以訪問內(nèi)存,且可以進行讀寫操作,而一般是通過內(nèi)存的首地址進行內(nèi)存的訪問,就是指針訪問。

  • 而OC中一般用來分配內(nèi)存的的函數(shù)是alloc/new/copy/mutablecopy(當(dāng)然還有clloc...等等),它們返回的都是指針,就是使用他們來生成對象并持有對象的。

  • 問題 3:持有?釋放?銷毀?對象... , 請看下表:

OC操作方法 對象的操作 retainCount
alloc/new/copy/mutablecopy等 生成并持有對象 1?
retain 持有對象 +1
release 釋放對象 -1
dealloc 銷毀對象 此時該值沒有意義
autorelease 在自動釋放池結(jié)束時,為里面的對象發(fā)送一條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:什么是自動釋放池?

  • 自動釋放池:在自動釋放池結(jié)束時,系統(tǒng)自動為里面的對象發(fā)送一條release消息(when the pool itself is drained)

  • 要使用自動釋放池就要使用NSAutoreleasePool對象

NSAutoreleasePool
  • NSAutoreleasePool它的方法
  • 使用方法:

  • 創(chuàng)建一個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方法,因為后面的方法會導(dǎo)致同一個對象被多次加入自動釋放池中。

addObject:方法

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

  • 補充autorelease方法


    autorelease方法
  • 問題 5:MRC下如何防止野指針訪問?

  • 野指針訪問:指向的內(nèi)存空間已經(jīng)被釋放了,但是指針還指向著已經(jīng)被釋放的內(nèi)存,此時的指針就是野指針。

  • 訪問了不存在的內(nèi)存,當(dāng)然會引起程序崩潰

  • 修改Xcode工程為MRC模式

  • 開啟對應(yīng)targets的僵尸對象檢測,詳細步驟如下:


    選擇Edit Scheme
勾選僵尸對象檢測
  • 情況 1:過快釋放了對象(不要理retaiinCount把注意力放在對象被持有的個數(shù)上)

  • retainCount的補充:


    只能用在調(diào)試階段,值是不可靠的
  • 程序代碼和運行結(jié)果


    tesh.m
main.m
指向異常的代碼
  • 問題 6:MRC下如何防止內(nèi)存泄漏?

  • 自己生成的對象,自己所持有

  • 非自己生成的對象,自己也能持有

  • 自己持有的對象不再需要時釋放

  • 非自己持有的對象無法釋放

補充:


持有對象
運行結(jié)果

疑問:mArrayCopy的retainCount是2 ?被持有者有兩個?

再來一次release

從這里就可以證明了,cope出來的新對象只是被mArrayCopy自己所持有而已,所以當(dāng)release一次的時候?qū)ο笠呀?jīng)被釋放了,如果再release就是野指針訪問了(注:直接看持有者有多少)。

代碼:

    /**
     *  alloc就是分配內(nèi)存的意思,返回了一個指向內(nèi)存首地址的指針
     */
    NSMutableArray *mArrayAlloc = [[NSMutableArray alloc] init];  // mArrayAlloc 持有對象
    /**
     *  new 就相當(dāng)于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 就表明了創(chuàng)建了一個新的內(nèi)存,并返回首地址(id 相當(dāng)于 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 就表明了創(chuàng)建了一個新的內(nèi)存,并返回首地址(id 相當(dāng)于 void *)
     */
    NSMutableArray *mArrayMutablecopy = [mArrayNew mutableCopy];//mArrayMutablecopy 持有了對象
  • 持有對象
源代碼
運行結(jié)果
明顯的野指針訪問了
  • 使用copy來獨立管理內(nèi)存
使用copy源代碼
內(nèi)容沒有改變
  • 如果內(nèi)存還在使用的話,當(dāng)然不要把對象賦值為nil

  • 對象之間相互持有的情況

  • 程序代碼


    Apple.h
Apple.m
Girl.h
Girl.m
main.m

如果要達到目的,apple讓girl也持有,就要在girl得到apple的時候持有一下,而可以做持有操作的是retain,來看看:

內(nèi)存泄漏

我們知道對象在最后銷毀的時候是調(diào)用了dealloc方法的,那么girl既然持有了apple那么在銷毀自己的時候是不是應(yīng)該把自己持有的東西給交出來(釋放掉),已死的對象不可能持有東西了吧,所以在girl的dealloc方法中加上apple釋放的代碼:

雖然上面的方法是可以的,但是有問題,問題如下:

retain

apple再持有一下[[Apple alloc] init],再給girl,直接翻譯都是問題,而且從封裝性來看,girl要持有apple應(yīng)該是自己去持有,也就是要自己進行retain,而不是要apple先retain再給girl,

代碼優(yōu)化:


retain去掉
set方法中進行retain

還有,如果我們從現(xiàn)實生活中考慮問題(面向?qū)ο笫乾F(xiàn)實世界的抽象),girl會不會只要一次apple呢?多要幾個~~

為了防止內(nèi)存泄漏,我得這么干,估計你看到這就想呵呵了:


正常釋放

再次優(yōu)化代碼,目的是只要girl再次要一個新的apple就給它持有,如果是拿原來的apple當(dāng)然不再次持有咯:

做if判斷
正常釋放

代碼修改成下面這樣就是真正的,girl直接的持有一個新的apple(新的內(nèi)存空間)了,不過結(jié)果是一樣的,正常釋放:


ARC( Automatic Reference Counting)內(nèi)存管理

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

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