一.開篇之初
-
記憶體管理解決的問題就是:
1)防止野指標的生成
(野指標:指向變數的指標還存在,但是所指向的記憶體已經被釋放,此時的指標就變成了野指標 --- 沒有指向 內容 的指標)
2)防止出現記憶體洩漏
(記憶體洩漏:指向記憶體空間的指標已經被釋放,但是該指標指向的記憶體空間還在記憶體中存在(被佔用) -- 沒有 “ 地址 ” 的記憶體)
3)合理使用記憶體,防止有限記憶體的大量消耗 -
Objective-C的記憶體管理有三種,其中iOS中能用的,就是MRC(手動引用計數)和ARC(自動引用計數,官方推薦使用);而另外一個垃圾回收機制,只能用在OS X系統中。
-
記憶體管理管理的範圍是,Objective-C
物件(基本資料型別由系統自動管理)。 -
MRC是基於引用計數的記憶體管理,是否釋放記憶體取決於引用計數是否為0;但注意,真正要研究並不是引用計數,而是物件是否被持有的問題。
-
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物件
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
- 建立一個NSAutoreleasePool物件
注意: 1 --> 建議使用autorelease方法,因為後面的方法會導致同一個物件被多次加入自動釋放池中。
2 --> 雖然兩個方法效果等同,但還是建議使用自動釋放池專門的drain方法。
-
補充autorelease方法
-
問題 5:MRC下如何防止野指標訪問?
- 野指標訪問:指向的記憶體空間已經被釋放了,但是指標還指向著已經被釋放的記憶體,此時的指標就是野指標。
- 訪問了不存在的記憶體,當然會引起程式崩潰
- 修改Xcode工程為MRC模式
- 開啟對應targets的殭屍物件檢測,詳細步驟如下:
- 情況 1:過快釋放了物件(不要理retaiinCount把注意力放在物件被持有的個數上)
- retainCount的補充:
- 程式程式碼和執行結果
-
問題 6:MRC下如何防止記憶體洩漏?
- 自己生成的物件,自己所持有
- 非自己生成的物件,自己也能持有
- 自己持有的物件不再需要時釋放
- 非自己持有的物件無法釋放 補充: 疑問:mArrayCopy的retainCount是2 ?被持有者有兩個? 從這裡就可以證明了,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來獨立管理記憶體
- 如果記憶體還在使用的話,當然不要把物件賦值為nil
- 物件之間相互持有的情況
- 程式程式碼 如果要達到目的,apple讓girl也持有,就要在girl得到apple的時候持有一下,而可以做持有操作的是retain,來看看: 我們知道物件在最後銷燬的時候是呼叫了dealloc方法的,那麼girl既然持有了apple那麼在銷燬自己的時候是不是應該把自己持有的東西給交出來(釋放掉),已死的物件不可能持有東西了吧,所以在girl的dealloc方法中加上apple釋放的程式碼: 雖然上面的方法是可以的,但是有問題,問題如下: apple再持有一下[[Apple alloc] init],再給girl,直接翻譯都是問題,而且從封裝性來看,girl要持有apple應該是自己去持有,也就是要自己進行retain,而不是要apple先retain再給girl,程式碼優化: 還有,如果我們從現實生活中考慮問題(物件導向是現實世界的抽象),girl會不會只要一次apple呢?多要幾個~~ 為了防止記憶體洩漏,我得這麼幹,估計你看到這就想呵呵了: 再次優化程式碼,目的是隻要girl再次要一個新的apple就給它持有,如果是拿原來的apple當然不再次持有咯: 程式碼修改成下面這樣就是真正的,girl直接的持有一個新的apple(新的記憶體空間)了,不過結果是一樣的,正常釋放: