爲什麼要進行內存管理。
由於移動設備的內存極其有限,所以每個APP所佔的內存也是有限制的,當app所佔用的內存較多時,系統就會發出內存警告,這時需要回收一些不需要再繼續使用的內存空間,比如回收一些不再使用的對象和變量等。
管理範圍:任何繼承NSObject的對象,對其他的基本數據類型無效。
本質原因是因爲對象和其他數據類型在系統中的存儲空間不一樣,其它局部變量主要存放於棧中,而對象存儲於堆中,當代碼塊結束時這個代碼塊中涉及的所有局部變量會被回收,指向對象的指針也被回收,此時對象已經沒有指針指向,但依然存在於內存中,造成內存泄露。
內存管理黃金法則:
The basic rule to apple is everything thatincreases the reference counter with alloc,[mutable]copy[WithZone:] or retainis in charge of the corresponding [auto]release.
如果一個對象使用了alloc,[mutable] copy,retain,那麼你必須使用相應的release或autonrelease
對象的基本結構
每個OC對象都有自己的引用計數器,是一個整數表示對象被引用的次數,即現在有多少東西在使用這個對象。對象剛被創建時,默認計數器值爲1,當計數器的值變爲0時,則對象銷燬。每個OC對象內部,都專門有4個字節的存儲空間來存儲引用計數器。
引用計數器的作用
判斷對象要不要回收的唯一依據就是計數器是否爲0,若不爲0則存在。
操作
給對象發送消息,進行相應的計數器操作。
Retain消息:使計數器+1,改方法返回對象本身
Release消息:使計數器-1(並不代表釋放對象)
retainCount消息:獲得對象當前的引用計數器值
對象的銷燬
當一個對象的引用計數器爲0時,那麼它將被銷燬,其佔用的內存被系統回收。
當對象被銷燬時,系統會自動向對象發送一條dealloc消息,一般會重寫dealloc方法,在這裏釋放相關的資源,dealloc就像是對象的“臨終遺言”。一旦重寫了dealloc方法就必須調用[super dealloc],並且放在代碼塊的最後調用(不能直接調用dealloc方法)。
一旦對象被回收了,那麼他所佔據的存儲空間就不再可用,堅持使用會導致程序崩潰(野指針錯誤)。
相關概念和使用注意
野指針錯誤:訪問了一塊壞的內存(已經被回收的,不可用的內存)。
殭屍對象:所佔內存已經被回收的對象,殭屍對象不能再被使用。(打開殭屍對象檢測)
空指針:沒有指向任何東西的指針(存儲的東西是0,null,nil),給空指針發送消息不會報錯
注意:不能使用[p retaion]讓殭屍對象起死復生。
內存管理原則
原則
只要還有人在使用某個對象,那麼這個對象就不會被回收;
只要你想使用這個對象,那麼就應該讓這個對象的引用計數器+1;
當你不想使用這個對象時,應該讓對象的引用計數器-1;
誰創建,誰release
如果你通過alloc,new,copy來創建了一個對象,那麼你就必須調用release或者autorelease方法
不是你創建的就不用你去負責
誰retain,誰release
只要你調用了retain,無論這個對象時如何生成的,你都要調用release
總結
有始有終,有加就應該有減。曾經讓某個對象計數器加1,就應該讓其在最後-1.
代碼規範:
1、只要調用了alloc,就必須有release(autorelease)
2、Set方法的代碼規範
//(1)基本數據類型:直接複製
-(void)setAge:(int)age
{
_age=age;
}
//(2)OC對象類型
-(void)setCar:(Car *)car
{
//1.先判斷是不是新傳進來的對象
If(car!=_car)
{
//2 對舊對象做一次release
[_car release];//若沒有舊對象,則沒有影響
//3.對新對象做一次retain
_car=[car retain];
}
}
3、dealloc方法的代碼規範
//一定要[super dealloc],而且要放到最後
//對self(當前)所擁有的的其他對象做一次release操作
-(void)dealloc
{
[_car release];
[super dealloc];
}
@property的內存參數
Retain(strong):對對象release舊值,retain新值(適用於OC對象類型)
Assign(week):直接賦值(默認,適用於非oc對象類型)
Copy:release舊值,copy新值
內存管理中的循環引用問題以及解決
案例:每個人有一張身份證,每張身份證對應一個人,不能使用#import的方式相互包含,這就形成了循環引用。
新的關鍵字:@class 類名;——解決循環引用問題,提高性能
@class僅僅告訴編譯器,在進行編譯的時候把後面的名字作爲一個類來處理。
(1)@class的作用:聲明一個類,告訴編譯器某個名稱是一個類
(2)開發中引用一個類的規範1)在.h文件中使用@class來聲明類 2)在.m文件中真正要使用到的時候,使用#import來包含類中的所有東西
(3)兩端循環引用的解決方法
一端使用retain,一端使用assign(使用assign的在dealloc中也不用再release)Autorelease
(一)基本用法
(1)會將對象放到一個自動釋放池中 (2)當自動釋放池被銷燬時,會對池子裏的所有對象做一次release (3)會返回對象本身 (4)調用完autorelease方法後,對象的計數器不受影響(銷燬時影響)
(二)好處
(1)不需要再關心對象釋放的時間 (2)不需要再關心什麼時候調用release
(三)使用注意
(1)佔用內存較大的對象,不要隨便使用autorelease,應該使用release來精確控制 (2)佔用內存較小的對象使用autorelease,沒有太大的影響
(四)錯誤寫法
(1)連續調用多次autorelease,釋放池銷燬時執行兩次release(-1嗎?) (2)Alloc之後調用了autorelease,之後又調用了release。
(五)自動釋放池
(1)在ios程序運行過程中,會創建無數個池子,這些池子都是以棧結構(先進後出)存在的。 (2)當一個對象調用autorelease時,會將這個對象放到位於棧頂的釋放池中
下面的例子進一步詳解了手動內存管理:
//Car.h
#import <Foundation/Foundation.h>
#import "Engine.h"
@interface Car : NSObject
{
//成員變量
Engine *_eng;
}
-(void)setEng:(Engine *)eng;
-(Engine *)eng;
-(void)run;
@end
#import "Car.h"
//Car.m
@implementation Car
//第一次
//-(void)setEng:(Engine *)eng{
//
// //對於傳入eng,引用計數+1
// _eng=[eng retain];
// //_eng=eng;
//}
//第二次
//-(void)setEng:(Engine *)eng{
// //如果不加release 內存泄露
// [_eng release];
// //對於傳入eng,引用計數+1
// _eng=[eng retain];
// //_eng=eng;
//}
//第三次
//-(void)setEng:(Engine *)eng{
//
// if (eng!=_eng) {
// //如果不加release 內存泄露
// [_eng release];
// //對於傳入eng,引用計數+1
// _eng=[eng retain];
// //_eng=eng;
// }
//
//}
//簡便寫法
-(void)setEng:(Engine *)eng{
[eng retain];
[_eng release];
_eng=eng;
}
-(Engine *)eng{
return _eng;
}
-(void)run{
NSLog(@"run");
}
- (void)dealloc
{
//根據誰擁有誰釋放的原則,再次釋放對象
NSLog(@"car銷燬");
[_eng release];
[super dealloc];
}
@end
//Engine.h
#import <Foundation/Foundation.h>
@interface Engine : NSObject
@end
//Engine.m
#import "Engine.h"
@implementation Engine
- (void)dealloc
{
NSLog(@"eng銷燬");
[super dealloc];
}
@end
//main.m
#import <Foundation/Foundation.h>
#import "test.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// //第一次測試
// Car *car=[Car new];
// Engine *eng=[Engine new];
// //實際需要計數引用+1
// car.eng=eng;
// //eng釋放
// [eng release];
// //car釋放
// [car release];
// //第二次測試
// Car *car=[Car new];
// Engine *e1=[Engine new];
// car.eng=e1;
// [e1 release];//e1不會被釋放
//
// Engine *e2=[Engine new];
// car.eng=e2;
// [e2 release];
// [car release];
//第三次測試
Car *car=[Car new];
Engine *e=[Engine new];
car.eng=e;
[e release];
car.eng=e;
[car release];
}
return 0;
}
-15.8.7
-15.8.12