OC+1-內存管理

內存管理的基本概念及範圍
內存管理:系統會向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、原則:只要還有人使用某個對象,該對象就不能被回收
                只要你想使用這個對象,就應該讓這個獨享的引用計數器+1retain
                當你不想使用時,應該讓對象的引用計數-1release
        2、誰創建,誰release
        3、誰retain,誰release
        4、總結:有始有終,有加就有減
內存管理研究的內容:
        1、野指針:1)定義的指針變量沒有初始化
                 2)指向的空間已經被釋放了
        2、內存泄露:棧區的P已經釋放了,而指向的堆區的空間還沒釋放,堆區的空間就被泄露了

//創建:new alloccopy
       
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  都在常量區
        //str2str4如果在常量區,地址應該一樣
        //但是地址不一樣,在堆區
        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];
}
此時的類方法,可以傳遞參數
發佈了43 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章