Objective-C 記憶體管理技巧與經驗

來源:互聯網
上載者:User

FW: http://blog.csdn.net/dboylx/archive/2009/02/15/3893770.aspx

 

讀了上篇記憶體管理文檔後,已經對Objective-c記憶體管理機制有了初步的瞭解。但僅賃那個其實還遠遠不夠,真正的項目中仍然會遇到記憶體管理上的問題。在具備了基礎的理論知識後,還需要些記憶體管理上的技巧與經驗。這點由其對從JAVA類語言過來的程式員,咱們實話實說,記憶體管理真不是咋們長項,更需要花更多的時間與精力來積累相關知識。不過話又說回來,人都說做生意的都講究吃虧在前賺錢在後。開始時候多賣點力,這座美麗的“金山”早晚是我們的。 ^_^

 
1,把對象視為記憶體。

每個對象執行個體的狀態都被儲存在不同的記憶體地區中。因此對象的建立與刪除動作等價於它所佔用記憶體的分配與回收。基於Foundation的類庫,都通過一個根對象“NSObject”或有同樣介面的其它類,為執行個體提供關聯記數的機制(包括延遲釋放的對象)。大多在Apple類庫的類與基於“NSObject”的子類或它介面的實作類別,都可以享有記憶體記數策略帶來的管理能力。

Apple類庫(Apple's frameworks)出台後,記憶體管理機制就被放在對象建立與銷毀的生命週期中(雖然有些地方還是些C的方法與結構)。你會發現這其實是件很有意思的事情,它不像C一樣有方法直接操作記憶體(malloc/free),也不像帶GC的語言(Java & Smalltalk & Ruby & Python ...)自動管理記憶體。 它是一種基於關聯記數與延時釋放(Autorelease機制,下面會講到)的機制管理記憶體,我們可以認為它是基於以上兩種記憶體管理方案中間,一個比較中庸的記憶體管理解決方案。

2,對象持有制

(Object Ownership,不知道要翻譯成啥,暫時叫他持有制,其實這個叫法本身就有些字面上的誤導。瞭解它的本質就OK了,不必太在意它叫什麼。^_^)

 基礎類庫與其它類庫都推薦我們下面兩個對象建立與銷毀的策略:
 a 如果你建立了一個對象,你有責任把它銷毀
 b 如果你想持有一個並不是你建立的對象,你需要”retain”它,並在不需要時”release”掉
 
 首先,對象的建立者就是它的擁有者,只有它的擁有者才可以銷毀它。貫徹這條策略會使你的代碼變的更簡單,更強壯,並且可以繞開很多的引用已經銷毀對象或記憶體泄露(沒有用的對象卻始終保持關係)造成的BUG。使用”NSAutoreleasePool”可以實現延遲釋放機制,建立者可以把銷毀的責任交給”NSAutoreleasePool”的對象執行個體(在下面會有詳細原理說明)。

3,對象的記憶體配置與初始化

 SomeClass *anInstance = [[SomeClass alloc] init];

這是一個傳統的建立對象的方法,首先分配一段記憶體,然後初始化。另外,在作業系統層面上還有記憶體區的概念,為了提高記憶體地區的定位使用能力,可以用”allocWithZone:”方法來嘗試分配一段指定的地區。”NSObject”的有狀態的子類都必需要擴充初始化方法,例如:

@interface CartesianCoordinate : NSObject
{
        NSNumber *abscissa;
        NSNumber *ordinate;
}

- (CartesianCoordinate *) initWithAbscissa: (NSNumber *)anAbscissa
                                 ordinate: (NSNumber *)anOrdinate;

@end

“NSObject”還提供了”copy,mutableCopy,copyWithZone,mutableCopyWithZone”方法可以分配記憶體,複屬性 來達到複製對象執行個體的目的。

4,對象的回收

  如果你不想再使用一個對象時,就發送”release”的訊息。當所有人都不在使用它,當沒有任何一個關聯時,它就會被自動發送”dealloc”方法回收。持有屬性的類,應該它在的”dealloc”方法內釋放所有它持有的對象執行個體。

@implementation CartesianCoordinate

...
- (void) dealloc
{
        [abscissa release];
        [ordinate release];

        return [super dealloc];
}

@end

5,對象執行個體的關聯記數

  其實你應該可以瞭解到,關聯記數是一個非常非常簡單的事情。每個對象都持有一關聯記數器”retain count”,它僅僅負責記錄關聯它的總個數。當一個對象以”init,initWith...”或其它複製方法建立時,這個數就被系統隱式的記為”1”。所有其它的對象可以發送”retain”訊息持有它,這個方法也僅僅是在這個記數上加”1”而已。相對應的,每一個”realease”方法也只是把這個數減”1”。當它為”0”時,這個對象被回收(調用它的”dealloc”方法)。你也可以調用”retainCount”方法來查詢這個數字。

- (void) notifyUserOfError: (NSString *)errorString
{
        NSMutableString *alertString = nil;

        alertString = [[NSMutableString alloc] initWithString:
                        @"The following error occurred: "];
        [alertString appendString: errorString];
        NSRunAlertPanel( alertString ...);
        [alertString release];

        return;
}

6,臨時對象與自動釋放方法

  就像你上面看到的,經常需要建立一個只用一次的對象,然後銷毀它。在上面的例子裡,當範圍定義好後它是很簡單的一件事。但存在一個問題,不能返回一個臨時的對象給調用者!!! 在C語言中有一個常用的方法,就是使用已經存在的靜態緩衝或是返回動態分配的記憶體,可能你已經想到了,它的調用者負責釋放它。這個方案跟咱們上面提到的記憶體管理原則相左,在Foundation架構中已經提供了一個更優雅的解決方案。通過延遲釋放機制讓建立臨時的對象可以最終自動釋放,看以下代碼:

- (void) notifyUserOfError: (NSString *)errorString
{
        NSMutableString *alertString = nil;

        alertString = [NSMutableString stringWithString:
                        @"The following error occurred: "];
        [alertString appendString: errorString];
        NSRunAlertPanel( alertString ...);

        return;
}

你可以看到”alertString”對象在建立後並沒有調用”release”,而這個方法的調用者也不用擔心要不要去釋放它,因為在這裡建立的對象是一個”autoreleased”對象,而這種對象會被自動釋放。一個自動釋放對象會在將來的某一時間被自動調用”release”方法。自動釋放對象被建立後,若沒有被顯示的”retain”,在有限的生命週期中被會自動銷毀。如果你想指定一個對象為自動釋放的話,可以調用”autorelease”方法。

    alertString = [NSMutableString stringWithString:
                    @"The following error occurred: "];

完全和以下是一樣的:

    alertString = 
        [[[NSMutableString alloc] initWithString:
            @"The following error occurred: "] autorelease];

有這麼一個貫例,就是像stringWithString:類似的方法都會建立一個自動釋放的執行個體,在類庫裡隨處可見。

7,自動釋放進階,讓我們更深一步瞭解它的工作原理

  雖然自動釋放對象的概念是如此簡單,但瞭解它更多的工作原理還是很有必要的。不然在我們的嵌入式裝置的開發中,仍然會走入記憶體漏洞深淵。

  其實,在我們的一個應用中,是有很多的”NSAutoreleasePool”對象執行個體的,就像它的命名一樣,它們用來收集所有自動釋放的對象。只要在調用”autorelease”方法後,它就會被加入到這個池中。在未來的某個時刻,一般指在”Foundation”與”AppKit”應用一個事件迴圈結束時,或者在響應完”WebObjects”類應用請求時,或調用”NSAutoreleasePool”對象的”release”方法。這裡需要注意,”NSAutoreleasePool”並不止一個,為什麼需要多個”NSAutoreleasePool”來管理記憶體呢?因為,在一個程式碼片段內就回收所有自動釋放對象是很有用處的,多線程應用中,每個線程可以擁有一個自動釋放池的棧,當你建立了一堆臨時對象時,而僅僅是在一段很短的上下文中,比如一個簡單迴圈,你並不希望在下面的代碼中,他們仍然佔用保寶貴的記憶體資源,你就可以為這段短小緊湊本機內容建立一個”NSAutoreleasePool”對象來管理他們:

- (id) findSomething
{
        id theObject = nil;
        // Whatever we're looking for
        NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
        // Autoreleased objects are now automatically placed in localPool.

        // Loop that creates many temporary objects
        while ( theObject == nil )
        {
            ...
            if ( [temporaryObject matchesSomeCondition] )
            {
                theObject = [temporaryObject retain];
                // We want this one
            }
        }

        // Get rid of all those temporary objects
        [localPool release];

        return [theObject autorelease];
}

上段代碼我們做了什麼:

A,我們建立了一個”NSAutoreleasePool”對象,把它壓到當前上下文中的記憶體管理池頂,在它下面的所有自動記憶體管理對象都被放入到這個池中。

B,我們調用了自動釋放對象”temporaryObject”的”retain”方法,使它的生命週期超過本地池的管理。

C,釋放池操作,同時把它從棧中POP出去。

D,緊接著,我們又在返回它前調用了 ”autorelease”方法,把這個對象放入當前池棧的TOP池中。

這東西有點繞,但原理其實還算簡單。如果上面的能明白了,恭喜你,Objective-C的水平又上了一個台階。

同理可證,還有一段更精練的代碼:

- (NSArray *) findAListOfThings
{
        NSMutableArray *thingArray =
            [[NSMutableArray alloc] initWithCapacity: 25];
        // The list of 25 things we're looking for
        NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
        NSAutoreleasePool *innerPool = nil;
        NSArray *largeObjectArray = nil;
        id temporaryObject = nil;
        NSEnumerator *arrayEnumerator = nil;

        // Loops that create many temporary objects
        while ( [thingArray count] != 25 )
        {
            largeObjectArray = [self fetchLotsOfObjects];
            // largeObjectArray is autoreleased and contained in the
            // outer autorelease pool
            arrayEnumerator = [largeObjectArray objectEnumerator];
            // Note that the enumerator itself is a temporary object!
            // It will be released by the outerPool

            // Create the inner pool on each iteration. When
            // a pool is created, it automatically becomes the
            // "top" pool on the current thread's stack of pools.
            innerPool = [[NSAutoreleasePool alloc] init];
            // autoreleased objects now go into innerPool

            while ( temporaryObject = [arrayEnumerator nextObject] )
            {
                ...
                if ( [temporaryObject matchesSomeCondition] )
                {
                    [thingArray addObject: temporaryObject];
                    // Collections retain their members
                }            
            }

            // Dispose temporary objects created on this iteration;
            // Note that the objects added to thingArray during this
            // iteration are also in innerPool and thus sent a release
            // message, but are not destroyed because they have been
            // retained by thingArray and so have an additional reference
            // (their retainCount > 1)
            [innerPool release];
        }

        [outerPool release];

        return [thingArray autorelease];
}

寫到這裡所有的概念都已經講完了,記憶體管理的東西也就是這麼多???
有興趣的可以在這裡再討論些執行個體?或是發我郵箱?MSN?

 

翻譯自:
http://www.stepwise.com/Articles/Technical/MemoryManagement.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.