1.什麼是內存管理
移動設備的內存極其有限,每個app所能占用的內存是有限制的
當app所占用的內存較多時,系統會發出內存警告,這時得回收一些不需要再使用的內存空間。比如回收一些不需要使用的對象、變量等
管理范圍:任何繼承了NSObject的對象需要去管理內存,但是對於對其他基本數據類型(int、char、float、double、struct、enum等)結構,枚舉等不用去關心內存
復制代碼
- (void)test
{
int a = 20;
int b = 10;
Person *person = [[Person alloc] init];
// 方法未退出之前 這三行代碼在內存中如圖
}
復制代碼
一旦test方法執行完畢,意味著局部變量的作用域也失效,那麼棧空間的局部變量系統會自動檢測回收。但是堆空間中動態產生的對象是還沒有被回收。如圖
可以看出即使已經沒有指針指向動態產生的對象了,但還是沒有被回收,因此需要手動管理釋放。釋放的方法是為對象發送一條消息。因此需要調用對象的某個方法來釋放對象。
那麼系統是怎麼知道此時的對象需不需要回收呢?這就涉及到了對象結構中的"引用計數"
2.對象結構
每個OC對象內部都有自己的引用計數器,它是一個整數,表示"對象被引用的次數",即有多少人正在使用這個OC對象
每個OC對象內部會自動設置4個字節的存儲空間來存儲引用計數器
3.引用計數器的作用
當使用alloc、new或者copy創建一個新對象時,新對象的引用計數器默認就是1
當一個對象的引用計數器值為0時,對象占用的內存就會被系統回收。換句話說,如果對象的計數器不為0,那麼在整個程序運行過程,它占用的內存就不可能被回收,除非整個程序已經退出
4.操作對象引用計數器的方法
給對象發送一條retain消息,可以使引用計數器值+1(retain方法返回對象本身)retain方法返回的是id類型,那麼哪個對象調用返回的就是自己
給對象發送一條release消息,可以使引用計數器值-1
可以給對象發送retainCount消息獲得當前的引用計數器值
5.對象的銷毀
當一個對象的引用計數器值為0時,那麼它將被銷毀,其占用的內存被系統回收
當一個對象被銷毀時,系統會自動向對象發送一條dealloc消息
一般會重寫dealloc方法,在這裡釋放相關資源,dealloc就像對象的遺言
一旦重寫了dealloc方法,就必須調用[super dealloc],並且放在最後面調用
不能直接調用dealloc方法
一旦對象被回收了,它占用的內存就不再可用,堅持使用會導致程序崩潰(野指針錯誤)
復制代碼
// alloc方法是給堆中分配內存 init方法和內存無關 此時retainCurrent為1
Person *p = [[Person alloc] init];
// 返回的就是對象本身 retainCurrent為2
[p retain];
// retainCurrent為1
[p release];
// retainCurrent為0 說明Person類對象被回收,那麼對應的在內存中的地址已經不可用了 此時的Person對象稱為“僵屍對象”但是此時p指針還是在指向Person類對象所對應的那塊不可用的地址此時的p指針稱為“野指針”
[p release];
復制代碼
6.開發中要注意的內存管理
默認情況下,Xcode是不會管理僵屍對象的,即使使用了一塊被釋放的內存也不會報錯。為了方便調試,應該開啟僵屍對象監控。如圖設置:
注意三個概念:
僵屍對象:已經被回收的對象,或者說對象所對應的內存地址已經不可用的對象稱為僵屍對象。僵屍對象不可用
野指針:指向一塊不可用內存地址或者指向僵屍對象的指針稱為野指針。給野指針發送消息會報 EXC_BAD_ACCESS錯誤
空指針:沒有指向任何指針變量稱為空指針,也意味著指針變量所存儲的值為0,nil,NULL 這樣可以避免野指針錯誤的發生
復制代碼
/********************************** Person.h **************************************/
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@end
/********************************** Person.m **************************************/
#import "Person.h"
@implementation Person
// 重寫父類NSOjbct的遺言方法 對象在被釋放之前一定會調用dealloc方法
- (void)dealloc
{
NSLog(@"對象在釋放之前會執行遺言方法被執行");
[super dealloc]; // 一定要調用 而且必須放在最後面
}
@end
/********************************** main.m **************************************/
#import <Foundation/Foundation.h>
#import "Person.h"
/*
main方法是一個死循環方法以保證程序能持續運行,除非用戶關閉程序或者是手機沒電,程序才能終止
那麼在main方法裡面的Person對象不就一直存在麼,因此必須在main方法裡面將對象回收
*/
int main(int argc, const char * argv[])
{
// alloc方法是給堆中分配內存 init方法和內存無關 此時retainCurrent為1
Person *p = [[Person alloc] init];
// 返回的就是對象本身 retainCurrent為2
[p retain];
// retainCurrent為1
[p release];
/*
retainCurrent為0
說明Person類對象被回收,那麼對應的在內存中的地址已經不可用了
此時的Person對象稱為“僵屍對象”
此時p指針還是在指向Person類對象所對應的那塊不可用的地址,此時p指針稱為“野指針”
*/
[p release];
/*
對象已經被回收,千萬別以為再給對象發送一個retain消息對象就可以“起死回生”應該節哀順變
執行retain方法會報錯,此時的p指針已稱為野指針執行代碼回報:野指針錯誤
*/
// [p retain];
/*
此時對象已經被回收稱為“僵屍對象了”不可以再訪問屬性
在執行p.age = 10;報錯:
-[Person setAge:]: message sent to deallocated instance 消息發送給了已經被釋放的對象
再次證明“僵屍對象不可以用”
*/
/*
一旦指針成為野指針再繼續向p指針所指的對象發送消息就會報錯:Exc_BAd_ACCESS
說明訪問了一塊壞內存(已經被回收、不可用的內存) “野指針錯誤”
那麼此時在對象回收之後將指針變量清空
那麼棧中的指針變量就不會再指向堆中類對象的內存地址了
*/
p = nil;
/*
指針變量內部所存儲的值已被清空,那麼指針已經無指向
再給指針發送任何消息指針會無任何響應,而且也不報錯因為OC中沒有空指針錯誤
*/
[p release];
[p release];
[p release];
[p release];
return 0;