內存管理的基本概念及範圍
內存管理:系統會向app發送memory waring消息,收到消息後,需要回收一些不需要再繼續使用的內存空間,比如回收一些不再使用的對象和變量等,否則程序會崩潰。
管理範圍:管理任何繼承NSObject的對象,對其他的基本數據類型無效(對象和其他數據類型在內存的存儲位置不一樣)
內存管理主要是對堆區中對象的內存管理
內存管理的原理及分類
原理:
對象的所有權及引用計數:任何對象都可能擁有一個或多個所有者,只要一個對象至少還擁有一個所有者,它就會繼續存在
引用計數器的作用:在每個OC對象的內部,都有專門8個字節的存儲空間來存儲引用計數器,判斷對象要不要回收(存在一種例外,對象值爲nil,引用計數爲0,但不回收空間)
對引用計數器的操作:1、retain消息:是計數器+1,該方法返回對象本身
2、release消息:是計數器-1(並不代表會釋放)
3、retainCount消息:獲得對象當前的引用計數器值
對象的銷燬:當一個對象的引用計數器爲0時,name它將被銷燬,其佔用的內存被系統回收。當對象被銷燬時,系統會自動向對象發送dealloc消息,一般會重寫dealloc方法。一旦重寫dealloc方法,必須調用[super
dealloc],並且放在代碼塊的最後調用(不能直接調用dealloc方法)。
一旦對象被回收了,那麼該空間就不可再用,堅持使用會導致程序崩潰(野指針錯誤)。
當使用alloc、new和copy出的對象,默認引用計數器爲1
內存管理分類:
OC提供了三種內存管理方式:1、Mannul Reference Counting(MRC,手動管理,在開發iOS4.1之前的版本的項目時,我們要自己負責使用引用計數來管理內存,比如要手動retain、release、autorelease等,而在其後的版本可以使用ARC,讓系統自己管理內存)
2、automatic reference counting (ARC,自動引用計數)
3、garbage collection(垃圾回收,iOS不支持垃圾回收)
開發中如何使用:需要理解MRC,但實際使用時儘量用ARC
手動內存管理快速入門
//用person實例一個對象
Person
*p = [Person
new];
//證明p有一個所有者
NSUInteger
count = [p retainCount];//用一個8字節無符號long類型來存儲
NSLog(@"count = %lu", count);
//是引用計數器+1
//讓p的所有者增加1,讓p1去指向
Person
*p1 = [p retain];
NSLog(@"p1.retainCount = %lu", [p1
retainCount]);
//回收對象,使retainCount == 0
[p release];
NSLog(@"p.retainCount = %lu", [p
retainCount]);
[p
release];//p執行後,此時對象被回收銷燬,自動調用dealloc方法
重寫dealloc方法
//dealloc方法,是對象的臨終遺言的方法
//對象被銷燬的時候,會默認的調用該方法
//注意:dealloc方法是系統自動調用的,不要手動調用
-(void)dealloc{
//先釋放子類子集的對象空間
NSLog(@"Person已經掛了");
//再釋放父類的
[super
dealloc];
}
內存管理的原則
內存管理:
對象如果不使用了,就應該回收他的空間,防止造成內存泄露
內存管理的範圍:
所有的繼承了NSObject的對象的內存管理,基本數據類型除外
內存管理的原則:
1、原則:只要還有人使用某個對象,該對象就不能被回收
只要你想使用這個對象,就應該讓這個獨享的引用計數器+1,retain
當你不想使用時,應該讓對象的引用計數-1,release
2、誰創建,誰release
3、誰retain,誰release
4、總結:有始有終,有加就有減
內存管理研究的內容:
1、野指針:1)定義的指針變量沒有初始化
2)指向的空間已經被釋放了
2、內存泄露:棧區的P已經釋放了,而指向的堆區的空間還沒釋放,堆區的空間就被泄露了
//創建:new
、alloc、copy
Dog
*bigYellowDog = [Dog
new];//1
NSLog(@"bigYellowDog.retainCount = %lu", [bigYellowDog
retainCount]);
Dog
*jd = [bigYellowDog retain];//新的對象,兩個對象地址一樣
NSLog(@"jd.retainCount = %lu", [jd
retainCount]);
//<Dog: 0x100206760>,<Dog: 0x100206760>
NSLog(@"%@,%@", bigYellowDog, jd);
//誰創建,誰release
[bigYellowDog release];
[jd
release];
最後,當retainCount == 0,系統自動調用dealloc方法
單個對象內存管理(野指針)
Xcode在默認情況下,爲了提高效率,不會開啓殭屍對象檢測Zombie->Edit scheme->run->
Dog
*byd = [Dog
new];
[byd
eat];
NSLog(@"byd.retainCount = %lu", byd.retainCount);//可以使用.調用此方法
[byd
release];//邏輯上釋放,物理上不可能刪除,已經被釋放的對象就是殭屍對象
//這就是野指針使用,已經釋放了的空間。但是讓殭屍對象去eat,不合理
//默認是不報錯的,設置開啓殭屍對象檢測
[byd
eat];
//同理,用已經釋放的殭屍對象訪問retainCount方法讀取引用計數器的值,也是野指針使用
NSLog(@"byd.retainCount = %lu", byd.retainCount);
注意:
1、
nil:是一個對象值,賦值爲空,便是nil
Nil:是一個類對象值
NULL:是一個通用指針(泛型指針)
[NSNull null]:是一個對象,用在不能使用nil的場合
2、不能使用殭屍對象,使其死而復生
[d
release];
//殭屍對象死而復生,並未開啓Zombie,不會報錯
[d retain];
//nil
給nil發送任何消息都沒有效果
//避免使用殭屍對象的方式:對象釋放完了以後,給對象賦值爲nil,提高程序健壯性
d =
nil;
[d
retain];//再使用殭屍對象,就不會報錯,不會響應任何消息
3、野指針操作
單個對象的內存泄露問題
1、創建和使用完後,沒有release
2、沒有遵守內存管理原則,新增的retain要和release對應
3、不當的使用nil,在release之前,使用nil賦值。導致最後並沒有release,因爲nil不會響應任何消息
4、在方法的內部retain,但是在main中只release一次,也滿足了內存管理的原則,此時的retain比較隱蔽,要注意
多個對象的內存管理(野指針)
Person
*fengjie = [Person
new];
Car
*bigBen = [Car
new];
bigBen.speed
=
180;
//給鳳姐一輛車
[fengjie setCar:bigBen];
[fengjie goLasa];
[bigBen release];//1->0 Car銷燬
//這句話報錯的原因 goLasa方法中使用了
_car(就是bigBen)
而bigBen已被銷燬
//要想下面的語句不報錯,必須保證_car存在
//要想在[bigBen release]之後,_car還存在
必須bigBen的引用對象不止一個
//即至少[bigBen retain]一次,但是這種方法明顯比較笨
//還可以在setCar方法裏這樣賦值_car
= [car retain];這樣比較健壯
//但是這樣之後,依舊存在[bigBen release]的問題
//爲了保證,人沒亡,就可以調用車
//我們可以在dealloc方法裏進行[bigBen
release]-》[_car release]
[fengjie
goLasa];
set方法的內存管理
原則:在一個類中,有其他類的對象(關聯關係)
基本數據類型作爲實例變量:直接賦值_speed = speed;
對象類型作爲另一個類的實例變量:先判斷是否是同一個對象,然後先release舊值,在retain新值
-(void) setCar:(Car
*)car {
//如果_car == car
就是同一個對象
//同一個對象,release之後就不可以在retain了,否則zombie就復活了
if
(_car
!= car) {
//先釋放前一個車,release舊值
[_car
release];
//retain新值,並且賦值給實例變量
_car
= [car retain];
}
@property參數
4.4之前:
1)@property+手動實現
2)@property int age; + @synthesize age;//get和set方法的聲明和實現都幫我們做了
3)@property int age; + @synthesize age = _age;//指定值賦值
4.4增強
@property int age;
1)生成_age
2)生成_age的get和set方法的聲明和實現
格式:@property(參數1,參數2。。)數據類型 方法名
參數類別:
1、原子性:1)atomic:對屬性加鎖,多線程下線程安全,默認值
2)nonatomic:對屬性不加鎖,多線程下不安全,但是速度快
2、讀寫屬性:1)readwrite:生成setter和getter方法,默認值
2)readonly:只生成getter方法
3、set方法處理:1)assign:直接賦值,默認值
2)retain:先release原來的值,再retain新值
3)copy:先release原來的值,再copy新值
替換set和get方法名稱:@property(setter = isVip:,getter = isVip)
//setVip: == isVip:
//vip == isVip
//同樣可以使用點語法
@class的使用
#import作用:把要引用的頭文件內容,拷貝到寫#import處
//如果引用的頭文件內容發生變化,則引用到這個文件的所有類就需要重新編譯,效率低
@class的使用:
格式:@class 類名;
@class XXX;
含義:告訴編譯器,XXX是一個類,至於類有哪些屬性和方法,此處不去檢測
好處:如果XXX文件內容發生變化,不需要重新編譯
注意:由於不知道屬性和方法,所以不可以直接在其他類中去使用,如何要使用,需要在實現文件中導入該類頭文件
所以,在實際開發中,.h中使用@class,.m中使用#import
1).h @class XX;
2).m #import “XX.h"
3)@class解決循環引入問題:交叉引用,循環依賴,使用#import會報錯
循環retain的問題
Person
*p = [Person
new];
Dog
*d = [Dog
new];
p.dog = d;
//p,d互掐
d.owner
= p;
[d release];
[p
release];
[p
release];//雖然也可解決問題,但有可能由於順序不同,導致某個對象多釋放一次
循環的retain會導致兩個對象都會內存泄露
推薦的方法:一端使用assign直接賦值,一端使用retain
NSString類的內存管理
@“abc”是字符串常量
棧區高地址,棧區-》堆區-》BSS-》數據區-》代碼區
//定義字符串
//字符串的常量池
//如果你需要的字符串在常量池已經存在了,不會分配內存空間
//使用字符串的時候,
//@"abc" stringWithString alloc initWithString
都在常量區
//str2和str4如果在常量區,地址應該一樣
//但是地址不一樣,在堆區
NSString
*str1 = @"abc";//常量區
NSString
*str2 = [NSString
stringWithFormat:@"aaa"];
NSString
*str3 = [NSString
stringWithString:@"abc"];//常量區
NSString
*str4 = [[NSString
alloc]
initWithFormat:@"aaa"];
NSString
*str5 = [[NSString
alloc]
initWithString:@"abc"];//常量區
NSLog(@"str1 = %@,%p,%lu", str1, str1, str1.retainCount);
NSLog(@"str2 = %@,%p,%lu", str2, str2, str2.retainCount);
NSLog(@"str3 = %@,%p,%lu", str3, str3, str3.retainCount);
NSLog(@"str4 = %@,%p,%lu", str4, str4, str4.retainCount);
NSLog(@"str5 = %@,%p,%lu", str5, str5, str5.retainCount);
危險用法:
retainCount對於系統有時候不準,自己的對象要把握retain和release呼應
while([a retainCount] > 0){
[a release];
}//死循環
autorelease基本使用
1、什麼是autorelease?
自動釋放池:1)在iOS程序運行過程中,會創建無數個池子,這些池子都是以棧結構(先進後出)存在的
2)當一個對象調用autorelease時,會將這個對象放到位於棧頂的釋放池中
iOS5.0之後,創建方式
@autoreleasepool
{//開始
。。。。
}//結束
autorelease:是一種支持引用計數的內存管理方式
它可以暫時的保存某個對象,然後在內存池自己排乾的時候對其中每個對象發release消息(每個對象只發送一次),注意,這裏只是發送release消息,如果當時的retaiCount依然不爲0,則該對象不會被釋放。可以用該方法保存某個對象,也要注意保存之後要釋放該對象。
自動釋放池的使用:
1)創建自動釋放池
2)加入自動釋放池,對象的引用計數不會改變
[對象 autorelease];
3)自動釋放池銷燬,會對池子裏的所有對象release一次
2、爲什麼會有autorelease?
OC內存管理原則:誰申請,誰釋放。如果一個方法需要返回一個對象,該對象合適釋放?方法內部是不會寫release來釋放對象的,因爲這樣立即釋放會返回一個空對象;調用者也不會主動釋放該對象,因爲遵循“誰申請,誰釋放”的原則。那麼此時,就發生了內存泄露。
針對這種情況,OC設計了autorelease,既能保證對象正確釋放,又能返回有效地對象
autorelease好處:
1)不需要關心對象釋放時間
2)不需要關心什麼時候調用release
3、autorelease的原理?
autorelease實際上只是把對release的調用延遲了,將對象一直保留到autoreleasepool釋放時,pool中的所有對象都會調用release
4、autorelease什麼時候被釋放?
1)5.0之前手動釋放
2)5.0之後自動釋放
autorelease的注意及錯誤使用
1)並不是所有放到自動釋放池中的代碼,產生的對象就會自動釋放。如果需要釋放,必須加入自動釋放池
Person
*p = [[Person
new]
autorelease];
2)如果對象調用了autorelease,但是調用的時候,沒有放在任何一個自動釋放池中,此對象也不會被加入自動釋放池
3)儘量不要把內存較大的對象放到自動釋放池中
4)不要在一個釋放吃中,多次使用[p autorelease];
5)在alloc後,自動釋放池結束之後,又進行[p
autorelease]操作,不允許。zombie操作
autorelease的應用場景
1、快速創建對象的類方法
Person
*p = [Person
person];
//person類方法:快速創建對象,並且管理對象的內存(加入自動釋放池)
//1)創建一個對象 P
//2)用完之後,系統把對象釋放掉
+(id) person
{
//能夠創建對象
//能夠幫我們把對象加入自動釋放池
return
[[[Person
alloc]
init]
autorelease];
}
2、完善快速創建對象的方法
//創建一個學生對象
Student
*stu = [Student
person];//返回的是Person類型
[stu
run];//動態類型,程序運行時才知道是什麼類型,其實是[Person
run];
只有將類方法修改一下纔可以
+(id) person
{
//能夠創建對象
//能夠幫我們把對象加入自動釋放池
//誰調用
返回誰
//Person調用
返回[Person run];
//Student調用
返回[Student run];
//Person---》self
return
[[[self
alloc]
init]
autorelease];
}
3、最終完善類方法
NSString
*str = [Student
person];
NSLog(@"str.lenth
= %ld", str.length);
沒完善之前,返回值類型是Student,指針類型是NSString。但是編譯不會報錯
將id類型改爲instancetype類型:能夠智能的檢測指針類型和返回值類型是否一致
+(instancetype) person
{
//能夠創建對象
//能夠幫我們把對象加入自動釋放池
//誰調用
返回誰
//Person調用
返回[Person run];
//Student調用
返回[Student run];
//Person---》self
return
[[[self
alloc]
init]
autorelease];
}
當我們需要指定值的創建對象和初始化,我們可以
-(instancetype) initWithAge:(int)age
{
//1、先初始化父類的,並且判斷是否成功
if
(self
= [super
init]) {
//2、初始化子類
_age
= age;
}
//3、返回self
return
self;
}
//+(instancetype) student
//{
// return [[self alloc] initWithAge:18];
//}
//這樣就寫死了
+(instancetype) studentWithAge:(int)age
{
return
[[self
alloc]
initWithAge:age];
}
此時的類方法,可以傳遞參數