內存泄露和內存管理

說到內存管理,我們自然會想到內存泄露問題。而且,內存管理的一個很重要的作用就是防止內存泄露。

內存泄漏 


   在此,談論的是程序設計中內存泄漏和錯誤的問題,不過,並不是所有的程序都有這一問題。首先,泄漏等一些內存方面的問題在有的程序語言中是不容易發生的。這些程序語言一般都認爲內存管理太重要了,所以不能由程序員來處理,最好還是由程序語言設計者來處理這些問題,這樣的語言有Perl、Java等等。   
   然而,在一些語言(最典型的就是C和C++)中,程序語言的設計者也認爲內存管理太重要,但必需由開發人員自己來處理。內存泄漏指的是程序員動態分配了內存,但是在使用完成後卻忘了將其釋放。除了內存泄漏以外,在開發人員自己管理內存的開發中,緩衝溢出、懸擺指針等其它一些內存的問題也時有發生。   

問題緣何產生 
   爲了讓程序能夠處理在編譯時無法預知的數據佔用內存的大小,所以程序必需要從操作系統實時地申請內存,這就是所謂的動態內存。這時候,就會出現程序申請到內存塊並且使用完成後,沒有將其歸還給操作系統的錯誤。更糟的情況是所獲取的內存塊的地址丟失,從而系統無法繼續識別、定位該內存塊。還有其它的問題,比如試圖訪問已經釋放的指針(懸擺指針),再如訪問已經被使用了的內存(內存溢出)的問題。
   
後果不容忽視 
   對於那些不常駐內存的程序來說,由於執行過程很短,所以即使有漏洞可能也不會導致特別嚴重的後果。不過對於一些常駐內存的程序(比如Web服務器Apache)來說,如果出現這樣的問題,後果將非常嚴重。因爲有問題的程序會不斷地向系統申請內存,並且不釋放內存,最終可能導致系統內存耗盡而導致系統崩潰。此外,存在內存泄漏問題的程序除了會佔用更多的內存外,還會使程序的性能急劇下降。對於服務器而言,如果出現這種情況,即使系統不崩潰,也會嚴重影響使用。    
   懸擺指針會導致一些潛在的隱患,並且這些隱患不容易暴發。它非常不明顯,因此很難被發現。在這三種存在的問題形式中,緩衝溢出可能是最危險的。事實上,它可能會導致很多安全性方面的問題(一個安全的程序包含很多要素,但是最重要的莫過於小心使用內存)。正如上面所述,有時也會發生同一內存塊被多次返還給系統的問題,這顯然也是程序設計上的錯誤。一個程序員非常希望知道在程序運行的過程中,使用內存的情況,從而能夠發現並且修正問題。   

如何處理 
   現在已經有了一些實時監測內存問題的技術。內存泄漏問題可以通過定時地終止和重啓有問題的程序來發現和解決。在比較新的Linux內核版本中,有一種名爲OOM(Out   Of   Memory )殺手的算法,它可以在必要時選擇執行Killed等程序。懸擺指針可以通過定期對所有已經返還給系統的內存置零來解決。解決內存溢出問題的方法則多種多樣。

(源引一塵的博客)ios框架的內存管理機制看似比較複雜,其實不然。一套把戲的制定,必然有它的道理。我認爲ios框架的內存機制的本質是爲了更好的讓設計人員方便管理內存,減少程序中的內存泄露,在內存管理難度與性能之間找一個最佳的平衡點。 假設你開闢了一塊新內存,並把內存地址付給指針A,後來你也想讓指針B也共享這塊內存,於是你把指針A付給指針B,這個時候,A和B都指向同一塊內存。如果A在沒有通知B的情況下,A擅自把把這塊內存釋放了,B使用這塊內存的時候,程序必然會crash掉。

有沒有辦法在釋放內存的時候,通知一下所有正在和將來要引用這塊內存的所有指針呢?答應是有的,只不過這種通知,不是主動式通知。在objective-c中,通過給堆中開闢的內存設計一個計數器,這個計數器的初始值爲0,在內存被開闢出來後,計數+1。當指針A不需要使用這塊內存的時候,只要把計數器-1,這時候計數器計數變成0,ios框架會釋放這塊內存。

梁靜茹有首歌是這樣唱的,“有些事你不必問,有些人你不必等”。其實,有些事,你也不必糾結。在剛接觸objective-c的時候,很多人會對oc中屬性的assign,retain,copy關鍵字感到迷惑。其實,我想說,書不必讀太多,況且有些書未必說清楚了。前面我說過,鬼把戲的制定,必然有它的道理。我們這樣想一下,對於int,bool等基本類型,這些直接在棧上開闢的內存,oc無需管理他們內存計數器,這個時候我們一般用assign;對於在堆中開闢的內存,我們需要維護內存的計數器,我們還引用上面的那個例子,首次創建內存後,內存計數器+1,當指針A付給指針B的時候,在這個時候,我們需要給內存計數器再+1,告訴其他指針,我B也引用了這塊內存,這個時候,retain正是做這個工作的,只是oc封裝了這個過程。其實copy更好理解,如果指針A和指針B不想相互牽扯,A管理A的內存,B管理B的內存,copy正是爲這個而生。

關於內存釋放是一個永恆的話題,我只想說,在+和-之間保持平衡,如果是框架+的,那就讓框架去-,別多管閒事。如果是你+的,那也不能偷懶。不是所有的成員變量都要設置屬性,釋放帶屬性的成員變量的時候,按照oc的約定,我們只需要用self.屬性=nil,至於爲什麼這樣,可以研究一下retain屬性的結構。

自動釋放池,並不是什麼好東西,如果不是萬般無奈的情況下,我建議不使用autorelease構建對象。但是,在一個方法中返回一個對象的指針,這個時候,我們不方便管理方法返回的對象,這種情況,用autorelease是比較明智的選擇。

不要輕易把autorelease對象付給retain屬性,因爲你很有可能忘記給屬性設置nil。

(源引工程師WWW的博客)1.  內總管理原則(引用計數)
    IOS的對象都繼承於NSObject,   該對象有一個方法:retainCount ,內存引用計數。 引用計數在很多技術都用到: window下的COM組件,多線程的信號量,讀寫鎖,思想都一樣。
    
   (一般情況下: 後面會討論例外情況)
    alloc      對象分配後引用計數爲1
    retain    對象的引用計數+1
    copy      copy 一個對象變成新的對象(新內存地址) 引用計數爲1 原來對象計數不變
   
    release            對象引用計數-1 如果爲0釋放內存
    autorelease      對象引用計數-1 如果爲0不馬上釋放,最近一個個pool時釋放    
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]);

[sMessage
retain]; //2
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]);
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]);
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]);
    內存管理的原則就是最終的引用計數要平衡,
   如果最後引用計數大於0  則會內存泄露
   如果引用 計數等於0還對該對象進行操作,則會出現內存訪問失敗,crash    所以儘量設置爲nil
   
   這兩個問題都很嚴重,所以請一定注意內存釋放和不用過後設置爲nil
2. autoReleasePool
   每個工程都有一個 main.m 文件: 內容如下:
   int main(int argc, char *argv[]) {
   
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}
   很明顯C語言的main 函數:
  NSAutoreleasePool 是用來做autorelease 變量釋放的,前面說了, autorelease不會馬上釋放,當他到了最近的pool release 時會檢查reatin count 是不是爲0, 爲0就釋放。
當我們在一段代碼時加入了大量的autorelease變量時,我們應該爲這段代碼加上
Autoreleasepool,其它時候不用
在返回一個對象時常常要返回一個autorelease 對象,因爲objectC 對象都是動態內存,沒有
棧的概念,所以不能像C/C++一樣返回一個對象到棧,只能用autorelease對象。
3. 成員變量與屬性
    實際情況並非上面那麼簡單,你可能需要在一個函數裏調用另一個函數分配的變量這時候
有兩個選擇:  類成員變量和使用屬性
   @interface TestMem: NSObject {
            TestObject   *m_testObject ;            // 成員變量
            TestObject   *testObject;                 //成員變量
    }
 成員變量與上面的內存管理是一致的,只是在不同的函數裏要保持引用計數加減的平衡
所以要你要每次分配的時候檢查是否上次已經分配了。是否還能調用
什麼時候用屬性?
  1. 把成員做爲public.
  2. outlet 一般聲明爲屬性( 這個內存於系統控制,但我們還是應該做一樣操作,後面會講)
  3. 如果很多函數都需要改變這個對象 ,或這個函數會觸發很多次,建議使用屬性。我們看看屬性函數展開後是什麼樣子:

// assign
-(void)setTestObject :(id)newValue{
    testObject= newValue;
}
// retain
-(void)setTestObject  :(id)newValue{
    if (testObject!= newValue) {
        [testObject release];
        testObject= [newValue retain];
    }
}
// copy
-(void)setTestObject :(id)newValue{
    if (testObject != newValue) {
        [testObject release];
        testObject = [newValue copy];
    }
}
asssign 相於於指針賦值,不對引用計數進行操作,注意原對象不用了,一定要把這個設置爲nil
retain 相當於對原對象的引用計數加1
copy 不對原對象的引用計數改變,生成一個新對象引用計數爲1
注意:
  self.testObject 左值調用的是setTestObject 方法. 右值爲get方法,get 方法比較簡單不用說了
  而 真接testObject 使用的是成員變量
  self.testObject = [[testObject alloc] init];   // 錯  reatin 兩次
  testObject =   [NSArray objectbyindex:0];  //錯 不安全,沒有retain 後面release會出錯
                                                                  如果testObject已有值也會mem leak       
4. 自動管理對象
   IOS 提供了很多static(+) 創建對象的類方法,這些方面是靜態的,可以直接用類名
調用如:
   NSString *testString = [NSString stringWithFormat:@"test" ];
   testString 是自動管理的對象,你不用relese 他,他有一個很大的retain count, release後數字不變。
 
5. 例外
   有一些通過alloc 生成的對象相同是自動管理的如:
   NSString  *testString  = [[NSString alloc] initWithString:@"test1"];
   retain count 同樣是很大的數,沒辦法release
   但爲了代碼對應,還是應該加上[ testString release];
   不然xcode的Analyze 會認識內存leak, 但Instruments leak 工具檢測是沒有的
6.view  內存管理
   通常我們編程會用到view,   在view 中的方法:
   
  viewDidload
  didReceiveMemoryWarning
 viewDidUnload
@property (retain) NSArray *iarrTestMem
        
        viewDidLoad
            init retain is         0
            (alloc)               +1 =1
                                    
        if(memwarning)
               didrecivememwarning  -0 =1
                                               
               didviewunload        -1
                                           =0  
                                  will load  viewDidLoad when next load view
        esle
               dealloc:             -1
                                      =0
    我們來看一個變量的生命週期
 
   當view被alloc
     A. 框架會自動調用viewDidLoad
         一般來說在viewDidLoad 中分配變量:
         假設爲alloc 現在變量的reatin 爲 0 +1 = 1;
     
     第一種情況:
      B. 如果這時候view被釋放,會調用 dealloc,
        這時候我們應該在dealloc裏release 這個變量現在爲0
     
     第二種情況:
       B. 這時候view被換到下層,如navigation的上級,不可顯示狀態:如果
          系統內存吃緊: 系統會發 消息,我們的didrecivememwarning 函數
          被調用 ,該函數是提醒我們應該釋放一些現在用不上的東西,特別是一些較大的
          如圖片,cache 數據等, 注意如果在這裏釋放了,代碼在用的地方要進行
          判斷,是否需要重新加載
       C. 他會調用didviewunload
          這時候我們要注意了,因爲他調用了didviewunload,在下次這個view被顯示的時候
         會再次調用didviewload, 我們在didviewload裏分配的孌童就會被分配兩次,所以我
         們在這裏一定要釋放didview裏分配的變量,不管直接分配的還是間接分配的,
         如發消息給其它函數分配的。 那我們在下次分配之前釋放一次行不行, 不行,因爲這
         時候內存吃緊了,你應該多釋放內存,不然你的程序可能會被系統kill. 還有雖然對
         [xxx release]  對nil 發 release 是沒問題的,但邏輯上讓人覺得很奇怪,以爲在別的
         地方分配過。 所以這裏應該釋放內存,
            如果你是一個屬性,用:
                  self.xxx = nil 比較好,他相當於幫你釋放了原來的,還把xxx設置爲了nil.
           如果不是一個屬性 :也最好把xxx= nil.  這樣比較安全。如果這個view不再被換入
          下一步直接調用 dealloc, 這時候你的dealloc裏的[xxx release], 如果這裏的xxx不爲nil你在
          didviewunload裏release 了,就非常危險
        D. 如果這時候view被釋放,會調用 dealloc,
           這時候我們應該在dealloc裏release
    
前面我們說了outlet的內存沒辦法管理(看下生命週期)
   
   
  @property (nonatomic,retain) IBOutlet UILabel *ilblTestMem;
         init retain is 2
         
         didviewLoad:     
                              = 2
         if(memwarning)
                 memwaring       - retain count -1
                                         = 1
                viewdidunload:   -1  
                                         =0
                              will load  viewDidLoad when next load view
        else
             dealloc:         -1
                                =1
      第一種情況
       A. didviewLoad:
                retain count 爲2
       B.  dealloc:
                 -1   retain count 爲1
      第二種情況:
       B.   memwarning
               系統會把 retain count 減到1
       C.   viewdidunload
               我們應該release 一次 這樣 retain count 到0,變量會馬上dealloc,更快的釋放內存
               注意用 self.ilblTestMem = nil  設置爲nil,  爲 dealloc 做準備
       D.  dealloc:
              -1
NSObject *sMessage = [[NSObjectalloc]init];
 //1
     
[sMessagerelease]; //1
NSLog(@"sMessage retainCount:%u",[sMessage retainCount]);

[sMessagerelease]; //0
//not crash  (retainCount also can call)
//crash  (can not call)
[sMessagerelease];
@property (nonatomic, retain) TestObject  testObject*; //爲testObject成員變量生成屬性方法
@end


iPhone/Mac Objective-C內存管理教程和原理剖析
前言
初學objectice-C的朋友都有一個困惑,總覺得對objective-C的內存管理機制琢磨不透,程序經常內存泄漏或莫名其妙的崩潰。我在這裏總結了自己對objective-C內存管理機制的研究成果和經驗,寫了這麼一個由淺入深的教程。希望對大家有所幫助,也歡迎大家一起探討。
 
此文涉及的內存管理是針對於繼承於NSObject的Class。
一 基本原理
Objective-C的內存管理機制與.Net/Java那種全自動的垃圾回收機制是不同的,它本質上還是C語言中的手動管理方式,只不過稍微加了一些自動方法。
1           Objective-C的對象生成於堆之上,生成之後,需要一個指針來指向它。
ClassA *obj1 = [[ClassA alloc] init];
 
2           Objective-C的對象在使用完成之後不會自動銷燬,需要執行dealloc來釋放空間(銷燬),否則內存泄露。
[obj1 dealloc];
         這帶來了一個問題。下面代碼中obj2是否需要調用dealloc?
ClassA *obj1 = [[ClassA alloc] init];
ClassA *obj2 = obj1;
[obj1 hello]; //輸出hello
[obj1 dealloc];
[obj2 hello]; //能夠執行這一行和下一行嗎?
[obj2 dealloc];
         不能,因爲obj1和obj2只是指針,它們指向同一個對象,[obj1 dealloc]已經銷燬這個對象了,不能再調用[obj2 hello]和[obj2 dealloc]。obj2實際上是個無效指針。
         如何避免無效指針?請看下一條。
 
3           Objective-C採用了引用計數(ref count或者retain count)。對象的內部保存一個數字,表示被引用的次數。例如,某個對象被兩個指針所指向(引用)那麼它的retain count爲2。需要銷燬對象的時候,不直接調用dealloc,而是調用release。release會讓retain count減1,只有retain count等於0,系統纔會調用dealloc真正銷燬這個對象。
ClassA *obj1 = [[ClassA alloc] init]; //對象生成時,retain count = 1
[obj1 release]; //release使retain count減1,retain count = 0,dealloc自動被調用,對象被銷燬
我們回頭看看剛剛那個無效指針的問題,把dealloc改成release解決了嗎?
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj1 hello]; //輸出hello
[obj1 release]; //retain count = 0,對象被銷燬
[obj2 hello];
[obj2 release];
         [obj1 release]之後,obj2依然是個無效指針。問題依然沒有解決。解決方法見下一條。
 
4           Objective-C指針賦值時,retain count不會自動增加,需要手動retain。
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj2 retain]; //retain count = 2
[obj1 hello]; //輸出hello
[obj1 release]; //retain count = 2 – 1 = 1
[obj2 hello]; //輸出hello
[obj2 release]; //retain count = 0,對象被銷燬
問題解決!注意,如果沒有調用[obj2 release],這個對象的retain count始終爲1,不會被銷燬,內存泄露。(1-4可以參考附件中的示例程序memman-no-pool.m)
這樣的確不會內存泄露,但似乎有點麻煩,有沒有簡單點的方法?見下一條。
 
5           Objective-C中引入了autorelease pool(自動釋放對象池),在遵守一些規則的情況下,可以自動釋放對象。(autorelease pool依然不是.Net/Java那種全自動的垃圾回收機制)
5.1          新生成的對象,只要調用autorelease就行了,無需再調用release!
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 但無需調用release
 
5.2          對於存在指針賦值的情況,代碼與前面類似。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj2 retain]; //retain count = 2
[obj1 hello]; //輸出hello
//對於obj1,無需調用(實際上不能調用)release
[obj2 hello]; //輸出hello
[obj2 release]; //retain count = 2-1 = 1
 
細心的讀者肯定能發現這個對象沒有被銷燬,何時銷燬呢?誰去銷燬它?(可以參考附件中的示例程序memman-with-pool.m)請看下一條。
 
6           autorelease pool原理剖析。(其實很簡單的,一定要堅持看下去,否則還是不能理解Objective-C的內存管理機制。)
6.1          autorelease pool不是天生的,需要手動創立。只不過在新建一個iphone項目時,xcode會自動幫你寫好。autorelease pool的真名是NSAutoreleasePool。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
6.2          NSAutoreleasePool內部包含一個數組(NSMutableArray),用來保存聲明爲autorelease的所有對象。如果一個對象聲明爲autorelease,系統所做的工作就是把這個對象加入到這個數組中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此對象加入autorelease pool中
6.3          NSAutoreleasePool自身在銷燬的時候,會遍歷一遍這個數組,release數組中的每個成員。如果此時數組中成員的retain count爲1,那麼release之後,retain count爲0,對象正式被銷燬。如果此時數組中成員的retain count大於1,那麼release之後,retain count大於0,此對象依然沒有被銷燬,內存泄露。
6.4          默認只有一個autorelease pool,通常類似於下面這個例子。
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
 
// do something
 
[pool release];
return (0);
} // main
所有標記爲autorelease的對象都只有在這個pool銷燬時才被銷燬。如果你有大量的對象標記爲autorelease,這顯然不能很好的利用內存,在iphone這種內存受限的程序中是很容易造成內存不足的。例如:
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i, j;
for (i = 0; i < 100; i++ )
{
 for (j = 0; j < 100000; j++ )
    [NSString stringWithFormat:@"1234567890"];//產生的對象是autorelease的。
}
[pool release];
return (0);
} // main
(可以參考附件中的示例程序memman-many-objs-one-pool.m,運行時通過監控工具可以發現使用的內存在急劇增加,直到pool銷燬時才被釋放)你需要考慮下一條。
 
7           Objective-C程序中可以嵌套創建多個autorelease pool。在需要大量創建局部變量的時候,可以創建內嵌的autorelease pool來及時釋放內存。(感謝網友hhyytt和neogui的提醒,某些情況下,系統會自動創建autorelease pool, 請參見第四章)
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i, j;
for (i = 0; i < 100; i++ )
{
 NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
 for (j = 0; j < 100000; j++ )
    [NSString stringWithFormat:@"1234567890"];//產生的對象是autorelease的。
 [loopPool release];
}
[pool release];
return (0);
} // main
 
二 口訣與範式
1           口訣。
1.1          誰創建,誰釋放(類似於“誰污染,誰治理”)。如果你通過alloc、new或copy來創建一個對象,那麼你必須調用release或autorelease。換句話說,不是你創建的,就不用你去釋放。
例如,你在一個函數中alloc生成了一個對象,且這個對象只在這個函數中被使用,那麼你必須在這個函數中調用release或autorelease。如果你在一個class的某個方法中alloc一個成員對象,且沒有調用autorelease,那麼你需要在這個類的dealloc方法中調用release;如果調用了autorelease,那麼在dealloc方法中什麼都不需要做。
1.2          除了alloc、new或copy之外的方法創建的對象都被聲明瞭autorelease。
1.3          誰retain,誰release。只要你調用了retain,無論這個對象是如何生成的,你都要調用release。有時候你的代碼中明明沒有retain,可是系統會在默認實現中加入retain。不知道爲什麼蘋果公司的文檔沒有強調這個非常重要的一點,請參考範式2.7和第三章。
2           範式。
範式就是模板,就是依葫蘆畫瓢。由於不同人有不同的理解和習慣,我總結的範式不一定適合所有人,但我能保證照着這樣做不會出問題。
2.1          創建一個對象。
ClassA *obj1 = [[ClassA alloc] init];
2.2          創建一個autorelease的對象。
ClassA *obj1 = [[[ClassA alloc] init] autorelease];
2.3          Release一個對象後,立即把指針清空。(順便說一句,release一個空指針是合法的,但不會發生任何事情)
[obj1 release];
obj1 = nil;
2.4          指針賦值給另一個指針。
ClassA *obj2 = obj1;
[obj2 retain];
//do something
[obj2 release];
obj2 = nil;
2.5          在一個函數中創建並返回對象,需要把這個對象設置爲autorelease
ClassA *Func1()
{
  ClassA *obj = [[[ClassA alloc]init]autorelease];
  return obj;
}
2.6          在子類的dealloc方法中調用基類的dealloc方法
-(void) dealloc
{
         …
         [super dealloc];
}
2.7          在一個class中創建和使用property。
2.7.1     聲明一個成員變量。
ClassB *objB;
2.7.2     聲明property,加上retain參數。
@property (retain) ClassB* objB;
2.7.3     定義property。(property的默認實現請看第三章)
@synthesize objB;
2.7.4     除了dealloc方法以外,始終用.操作符的方式來調用property。
self.objB 或者objA.objB
2.7.5     在dealloc方法中release這個成員變量。
[objB release];
示例代碼如下(詳細代碼請參考附件中的memman-property.m,你需要特別留意對象是在何時被銷燬的。):
@interface ClassA : NSObject
{
         ClassB* objB;
}
 
@property (retain) ClassB* objB;
@end
 
@implementation ClassA
@synthesize objB;
-(void) dealloc
{
         [objB release];
         [super dealloc];
}
@end
2.7.6     給這個property賦值時,有手動release和autorelease兩種方式。
void funcNoAutorelease()
{
         ClassB *objB1 = [[ClassB alloc]init];
         ClassA *objA = [[ClassA alloc]init];
         objA.objB = objB1;
         [objB1 release];
         [objA release];
}
 
void funcAutorelease()
{
         ClassB *objB1 = [[[ClassB alloc]init] autorelease];
         ClassA *objA = [[[ClassA alloc]init] autorelease];
         objA.objB = objB1;
}
 
三 @property (retain)和@synthesize的默認實現
在這裏解釋一下@property (retain) ClassB* objB;和@synthesize objB;背後到底發生了什麼(retain property的默認實現)。property實際上是getter和setter,針對有retain參數的property,背後的實現如下(請參考附件中的memman-getter-setter.m,你會發現,結果和memman-property.m一樣):
@interface ClassA : NSObject
{
         ClassB *objB;
}
-(ClassB *) getObjB;
-(void) setObjB:(ClassB *) value;
@end
 
@implementation ClassA
-(ClassB*) getObjB
{
         return objB;
}
 
-(void) setObjB:(ClassB*) value
{
         if (objB != value)
         {
                   [objB release];
                   objB = [value retain];
         }
}
 
在setObjB中,如果新設定的值和原值不同的話,必須要把原值對象release一次,這樣才能保證retain count是正確的。
由於我們在class內部retain了一次(雖然是默認實現的),所以我們要在dealloc方法中release這個成員變量。
-(void) dealloc
{
         [objB release];
         [super dealloc];
}
 
四 系統自動創建新的autorelease pool
在生成新的Run Loop的時候,系統會自動創建新的autorelease pool(非常感謝網友hhyytt和neogui的提醒)。注意,此處不同於xcode在新建項目時自動生成的代碼中加入的autorelease pool,xcode生成的代碼可以被刪除,但系統自動創建的新的autorelease pool是無法刪除的(對於無Garbage Collection的環境來說)。Objective-C沒有給出實現代碼,官方文檔也沒有說明,但我們可以通過小程序來證明。
在這個小程序中,我們先生成了一個autorelease pool,然後生成一個autorelease的ClassA的實例,再在一個新的run loop中生成一個autorelease的ClassB的對象(注意,我們並沒有手動在新run loop中生成autorelease pool)。精簡的示例代碼如下,詳細代碼請見附件中的memman-run-loop-with-pool.m。
int main(int argc, char**argv)
{
         NSLog(@"create an autorelasePool\n");
         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];   
 
         NSLog(@"create an instance of ClassA and autorelease\n");
         ClassA *obj1 = [[[ClassA alloc] init] autorelease];
         NSDate *now = [[NSDate alloc] init];
         NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
                   interval:0.0
                   target:obj1
                   selector:@selector(createClassB)
                   userInfo:nil
                   repeats:NO];
         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
         [timer release];
         [now release];
         [runLoop run]; //在新loop中調用一函數,生成ClassB的autorelease實例
 
         NSLog(@"releasing autorelasePool\n");
         [pool release];
         NSLog(@"autorelasePool is released\n");
         return 0;
}
輸出如下:
create an autorelasePool
create an instance of ClassA and autorelease
create an instance of ClassB and autorelease
ClassB destroyed
releasing autorelasePool
ClassA destroyed
autorelasePool is released
注意在我們銷燬autorelease pool之前,ClassB的autorelease實例就已經被銷燬了。
有人可能會說,這並不能說明新的run loop自動生成了一個新的autorelease pool,說不定還只是用了老的autorelease pool,只不過後來drain了一次而已。我們可以在main函數中不生成autorelease pool。精簡的示例代碼如下,詳細代碼請見附件中的memman-run-loop-without-pool.m。
int main(int argc, char**argv)
{
         NSLog(@"No autorelasePool created\n");
 
         NSLog(@"create an instance of ClassA\n");
         ClassA *obj1 = [[ClassA alloc] init];
         NSDate *now = [[NSDate alloc] init];
         NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
                   interval:0.0
                   target:obj1
                   selector:@selector(createClassB)
                   userInfo:nil
                   repeats:NO];
         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
         [timer release];
         [now release];
         [runLoop run]; //在新loop中調用一函數,生成ClassB的autorelease實例
         NSLog(@"Manually release the instance of ClassA\n");
         [obj1 release];
 
         return 0;
}
輸出如下:
No autorelasePool created
create an instance of ClassA
create an instance of ClassB and autorelease
ClassB destroyed
Manually release the instance of ClassA
ClassA destroyed
我們可以看出來,我們並沒有創建任何autorelease pool,可是ClassB的實例依然被自動銷燬了,這說明新的run loop自動創建了一個autorelease pool,這個pool在新的run loop結束的時候會銷燬自己(並自動release所包含的對象)。
 
 
補充說明
在研究retain count的時候,我不建議用NSString。因爲在下面的語句中,
NSString *str1 = @”constant string”;
str1的retain count是個很大的數字。Objective-C對常量字符串做了特殊處理。
當然,如果你這樣創建NSString,得到的retain count依然爲1
NSString *str2 = [NSString stringWithFormat:@”123”];

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章