Cocoa ve Objective-C ile referans sayımını anlama


122

İPhone SDK ile oynamak için Objective-C ve Cocoa'ya yeni bir göz atmaya başlıyorum. C'ler mallocve freekonsept konusunda oldukça rahatım , ancak Cocoa'nın referans sayma şeması kafamı biraz karıştırdı. Anladığında çok zarif olduğu söylendi, ama henüz kamburlaşmadım.

Nasıl release, retainve autoreleaseçalışma ve bunların kullanımı hakkında konvansiyonlar nelerdir?

(Ya da başarısız olursa, anlamanıza yardımcı olan ne okudunuz?)

Yanıtlar:


148

İle başlayalım retainve release; autoreleasetemel kavramları anladıktan sonra gerçekten özel bir durumdur.

Cocoa'da her nesne, kendisine kaç kez başvurulduğunu izler (özellikle, NSObjecttemel sınıf bunu uygular). retainBir nesneyi çağırarak , referans sayısını bir artırmak istediğinizi söylüyorsunuz. Çağırarak release, nesneyi bıraktığınızı söylersiniz ve referans sayısı azalır. Çağrıldıktan sonra releasereferans sayısı artık sıfırsa, o nesnenin belleği sistem tarafından serbest bırakılır.

Bunun temel yoldan farklı olması mallocve freeherhangi bir nesnenin, kullandıkları belleği serbest bıraktığınız için sistemin diğer bölümlerinin çökmesi konusunda endişelenmesine gerek olmamasıdır. Herkesin birlikte oynadığını ve kurallara göre sakladığını / serbest bıraktığını varsayarsak, bir kod parçası nesneyi koruduğunda ve sonra serbest bıraktığında, nesneye referans veren diğer herhangi bir kod parçası da etkilenmeyecektir.

Ne bazen kafa karıştırıcı olabilir aramak edildiğini belirttiği durumunu bildiğim olduğunu retainve release. Genel kuralım, bir nesneye bir süre bağlı kalmak istiyorsam (örneğin, bir sınıftaki üye değişkeni ise), nesnenin referans sayısının beni bildiğinden emin olmam gerektiğidir. Yukarıda açıklandığı gibi, bir nesnenin referans sayısı çağrı yapılarak artırılır retain. Geleneksel olarak, nesne bir "init" yöntemiyle oluşturulduğunda da artırılır (gerçekten 1'e ayarlanır). Her iki durumda da, releaseişim bittiğinde nesneyi aramak benim sorumluluğumdadır . Yapmazsam, bir hafıza sızıntısı olacak.

Nesne oluşturma örneği:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Şimdi için autorelease. Otomatik serbest bırakma, sisteme bu nesneyi bir süre sonra serbest bırakmasını söylemenin uygun (ve bazen gerekli) bir yolu olarak kullanılır. Sıhhi tesisat açısından bakıldığında, autoreleaseçağrıldığında, mevcut iş parçacığı NSAutoreleasePoolçağrı hakkında uyarılır. Artık NSAutoreleasePoolbir fırsat yakaladığında (olay döngüsünün geçerli yinelemesinden sonra) releasenesneyi çağırabileceğini biliyor . Programcılar olarak bizim bakış releaseaçımızdan, bizi aramaya özen gösterir , bu yüzden buna gerek yoktur (ve aslında yapmamalıyız).

Unutulmaması gereken önemli nokta, (yine geleneksel olarak) tüm nesne oluşturma sınıfı yöntemlerinin otomatik olarak yayımlanan bir nesne döndürmesidir. Örneğin, aşağıdaki örnekte, "s" değişkeninin referans sayısı 1'dir, ancak olay döngüsü tamamlandıktan sonra yok edilecektir.

NSString* s = [NSString stringWithString:@"Hello World"];

Bu dizeye takılmak istiyorsanız, retainaçıkça aramanız ve sonra releaseişiniz bittiğinde açıkça aramanız gerekir .

Aşağıdaki (çok uydurulmuş) kod parçasını düşünün ve autoreleasegerekli olduğu bir durum göreceksiniz :

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Tüm bunların biraz kafa karıştırıcı olduğunun farkındayım - yine de bir noktada tıklanacak. İşte başlamanıza yardımcı olacak birkaç referans:

  • Apple'ın bellek yönetimine girişi .
  • Mac OS X için Cocoa Programming (4th Edition) , Aaron Hillegas - birçok harika örnek içeren çok iyi yazılmış bir kitap. Bir öğretici gibi okur.
  • Gerçekten dalıyorsanız, Big Nerd Ranch'a gidebilirsiniz . Bu, yukarıda bahsedilen kitabın yazarı Aaron Hillegas tarafından işletilen bir eğitim tesisidir. Orada birkaç yıl önce Kakaoya Giriş kursuna katıldım ve bu öğrenmenin harika bir yoluydu.

8
"Otomatik serbest bırakma çağrısı yaparak referans sayısını geçici olarak çarpıyoruz" yazdınız. Bence bu yanlış; otomatik serbest bırakma
LKM

2
"Şimdi otomatik yayın için. Otomatik yayın, sisteme bu nesneyi bir süre sonra serbest bırakmasını söylemenin uygun (ve bazen gerekli) bir yolu olarak kullanılır." Giriş cümlesi olarak bu yanlış. Sisteme "serbest bırak" demez, alıkoyma sayısını azaltmasını söyler.
mmalc

3
İyi açıklama için çok teşekkürler. Hâlâ belirsiz olan tek bir şey. Eğer otomatik NSString* s = [[NSString alloc] initWithString:@"Hello World"];yayımlanan bir nesne döndürürse (siz onu yazarken) neden sadece bir tane yapmam return [s autorelease];ve onu yeniden "otomatik yayınlama" olarak ayarlamam gerekiyor, sadece değil return s?
znq

3
@Stefan: otomatik olarak yayımlanan [[NSString alloc] initWithString:@"Hello World"]bir nesne döndürmez. Her allocçağrıldığında, referans sayısı 1'e ayarlanır ve serbest bırakıldığından emin olmak bu kodun sorumluluğundadır. [NSString stringWithString:]Çağrı, diğer taraftan, yok bir autoreleased nesneyi döndürür.
Matt Dillard

6
Eğlenceli bilgiler: Yanıt @ "" ve NSString kullandığından, dizeler boyunca sabittir ve bu nedenle, mutlak tutma sayısı hem sabit hem de tamamen alakasız olacaktır .... yanıtı hiçbir şekilde yanlış yapmaz, sadece mutlak tutma sayılarının asla gerçekten endişelenmeniz gereken bir şey olmadığı gerçeğini güçlendirir.
bbum

10

Tutma / bırakma sürecini anlarsanız, o zaman yerleşik Kakao programcıları için açık olan iki altın kural vardır, ancak maalesef bunu yeni başlayanlar için nadiren açık bir şekilde ifade edilir.

  1. Bir nesneyi döndüren bir fonksiyon varsa alloc, createya copyadında ardından nesne sizin. İşin [object release]bittiğinde aramalısın . Veya CFRelease(object)bir Core-Foundation nesnesiyse.

  2. Adında bu kelimelerden biri YOKSA, nesne başka birine aittir. [object retain]İşlevinizin bitiminden sonra nesneyi saklamak istiyorsanız aramanız gerekir .

Kendinizi yarattığınız işlevlerde de bu geleneği takip etmeniz çok iyi olacaktır.

(Nitpickers: Evet, maalesef bu kuralların istisnası olan ancak nadir olan birkaç API çağrısı var).


11
Bu eksik ve yanlış. İnsanların sadece ilgili belgelere işaret etmek yerine kuralları neden tekrar etmeye çalıştıklarını anlamaya devam ediyorum: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc

4
Özellikle Temel Temel kuralları Cocoa'nınkilerden farklıdır; bkz developer.apple.com/documentation/CoreFoundation/Conceptual/...
mmalc

1
Ben de katılmıyorum. Bir işlev sahip olmak istemediği bir şeyi döndürüyorsa, onu otomatik olarak serbest bırakmalıdır. İşi korumak (istenirse) işlevlerin işini arayan kişidir. Çağrılan herhangi bir yöntemin adıyla hiçbir ilgisi olmamalıdır. Bu, nesnelerin sahipliğinin belirsiz olduğu daha çok C Stili kodlamasıdır.
Sam

1
Afedersiniz! Sanırım olumsuz oylama konusunda aceleci davrandım. Bellek Yönetimi Kuralları Cevabınız neredeyse Apple doc'tan alıntı yapıyor.
Sam

8

Masaüstü için kod yazıyorsanız ve Mac OS X 10.5'i hedefleyebiliyorsanız, en azından Objective-C çöp toplama kullanımına bakmalısınız. Bu, geliştirmenizin çoğunu gerçekten basitleştirecek - bu nedenle Apple, ilk etapta onu oluşturmak ve iyi performans göstermesini sağlamak için tüm çabayı harcadı.

GC kullanılmadığında bellek yönetimi kurallarına gelince:

  • Eğer kullanarak yeni bir nesne oluşturmak durumunda +alloc/+allocWithZone:, +new, -copyveya -mutableCopyveya eğer -retainbir nesne, bunu sahipliğini alıyor ve gönderilir sağlamalıdır -release.
  • Başka bir şekilde bir nesne alırsanız , onun sahibi değilsinizdir ve gönderilmesini sağlamamalısınız-release .
  • Bir nesnenin gönderildiğinden emin olmak -releaseistiyorsanız, onu kendiniz gönderebilirsiniz veya nesneyi gönderebilirsiniz -autoreleaseve mevcut otomatik serbest bırakma havuzu , havuz boşaltıldığında onu -release(her alındığında bir kez -autorelease) gönderir.

Genelde -autorelease, nesnelerin geçerli olay süresince yaşamasını sağlamanın bir yolu olarak kullanılır, ancak Cocoa'nın olay işlemesini çevreleyen bir otomatik serbest bırakma havuzu olduğundan, daha sonra temizlenir. Cocoa'da, nesneleri otomatik olarak serbest bırakılan bir arayana döndürmek, arayanın kendisinin bırakması gereken nesneleri döndürmekten çok daha yaygındır.


6

Objective-C, her bir Nesnenin bir referans sayısına sahip olduğu anlamına gelen Referans Sayımı kullanır . Bir nesne oluşturulduğunda, referans sayısı "1" dir. Basitçe söylemek gerekirse, bir nesneye atıfta bulunulduğunda (yani, bir yerde saklandığında), "alıkonulur", yani referans sayısı bir artar. Bir nesneye artık ihtiyaç duyulmadığında, "serbest bırakılır", yani referans sayısı bir azaltılır.

Bir nesnenin referans sayısı 0 olduğunda, nesne serbest bırakılır. Bu temel referans sayımıdır.

Bazı diller için referanslar otomatik olarak artırılır ve azaltılır, ancak amaç-c bu dillerden biri değildir. Böylelikle programcı tutma ve yayınlamadan sorumludur.

Bir yöntem yazmanın tipik bir yolu şudur:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Kodun içindeki herhangi bir edinilmiş kaynağı serbest bırakmayı hatırlama ihtiyacı sorunu hem sıkıcı hem de hataya açıktır. Objective-C, bunu çok daha kolay hale getirmeyi amaçlayan başka bir konsept sunar: Otomatik Yayın Havuzları. Otomatik yayın havuzları, her iş parçacığına yüklenen özel nesnelerdir. NSAutoreleasePool'u ararsanız, oldukça basit bir sınıftır.

Bir nesne kendisine gönderilen bir "otomatik serbest bırakma" mesajı aldığında, nesne bu mevcut iş parçacığı için yığın üzerinde oturan herhangi bir otomatik serbest bırakma havuzunu arayacaktır. Nesneyi, gelecekte bir noktada, genellikle havuzun kendisi serbest bırakıldığında bir "bırakma" mesajı göndermek için bir nesne olarak listeye ekleyecektir.

Yukarıdaki kodu alarak, şunu söyleyerek daha kısa ve daha kolay okunacak şekilde yeniden yazabilirsiniz:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Nesne otomatik olarak yayınlandığından, artık üzerinde açıkça "yayın" olarak adlandırmamıza gerek yoktur. Bunun nedeni, bazı otomatik yayın havuzunun bunu bizim için daha sonra yapacağını biliyoruz.

Umarım bu yardımcı olur. Wikipedia makalesi referans sayma konusunda oldukça iyidir. Otomatik yayın havuzları hakkında daha fazla bilgiyi burada bulabilirsiniz . Ayrıca, Mac OS X 10.5 ve sonraki sürümler için derliyorsanız, Xcode'a çöp toplama etkinleştirilmiş olarak derlemesini söyleyebilirsiniz, bu da tutma / bırakma / otomatik bırakma işlemini tamamen yok saymanıza olanak tanır.


2
Bu çok yanlış. Gösterilen örneklerin hiçbirinde someObject sürümü veya otomatik kiralama göndermeye gerek yoktur.
mmalc

6

Joshua (# 6591) - Mac OS X 10.5'teki Çöp toplama öğeleri oldukça havalı görünüyor, ancak iPhone için mevcut değil (veya uygulamanızın Mac OS X'in 10.5 öncesi sürümlerinde çalışmasını istiyorsanız).

Ayrıca, bir kitaplık veya yeniden kullanılabilecek bir şey yazıyorsanız, GC modunu kullanmak, kodu kullanan herkesi GC modunu da kullanmaya kilitler, böylece anladığım kadarıyla, geniş çapta yeniden kullanılabilir kod yazmaya çalışan herkes yönetme eğilimindedir. manuel olarak hafıza.


2
Hem GC hem de referans saymayı destekleyen hibrit bir çerçeve yazmak tamamen mümkündür.
mmalc

6

Her zaman olduğu gibi, insanlar referans materyali yeniden ifade etmeye başladıklarında neredeyse her zaman bir şeyleri yanlış anlar veya eksik bir açıklama sunarlar.

Apple, Kakao için Bellek Yönetimi Programlama Kılavuzu'nda Kakao'nun bellek yönetim sisteminin tam bir açıklamasını sunar ve sonunda Bellek Yönetimi Kurallarının kısa ama doğru bir özeti bulunur .




2
Aslında bu çok daha iyi bir tek sayfalık özet: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau

6

50 $ 'ı düşürmeyi ve Hillegass kitabını almayı düşünmek isteyebileceğinizden başka saklama / bırakma özelliğine eklemeyeceğim, ancak Instruments araçlarını uygulamanızın geliştirilmesinde çok erken kullanmaya başlamanızı şiddetle tavsiye ederim (hatta sizin ilk!). Bunu yapmak için Çalıştır-> Performans araçlarıyla başlayın. Mevcut araçlardan sadece biri olan ancak piyasaya sürmeyi unuttuğunuzda size göstermeye yardımcı olacak Leaks ile başlayacağım. Size ne kadar bilgi sunulacağını göz korkutmaktan vazgeçin. Ancak hızlı bir şekilde kalkmak ve devam etmek için bu eğiticiye göz atın :
KAKAO ÖĞRETİMİ: BELLEK SIZINTILARINI ENSTRÜMANLARLA DÜZELTME

Aslında sızıntıları zorlamaya çalışmak , sırayla onları nasıl önleyeceğinizi öğrenmenin daha iyi bir yolu olabilir! İyi şanslar ;)


5

Matt Dillard şunu yazdı :

dönüş [[s otomatik yayın] yayın];

Sallanmasını yok değil nesne korur. Otomatik serbest bırakma, daha sonra yayınlanmak üzere sıraya koyar. Orada bir tahliye beyanı istemezsiniz.




4

NilObject'in cevabı iyi bir başlangıçtır. Manuel bellek yönetimiyle ilgili bazı tamamlayıcı bilgiler ( iPhone'da gereklidir ).

Kişisel olarak alloc/initbir nesne iseniz , referans sayısı 1 ile gelir. Artık gerekmediğinde onu arayarak [foo release]veya arayarak temizlemekten sorumlusunuz [foo autorelease]. serbest bırakma, nesneyi hemen temizler, oysa otomatik serbest bırakma, nesneyi daha sonra otomatik olarak bırakacak olan otomatik yayın havuzuna ekler.

otomatik serbest bırakma , öncelikle söz konusu nesneyi döndürmeniz gereken bir yönteminiz olduğunda ( bu nedenle onu manuel olarak serbest bırakamazsınız, aksi takdirde bir sıfır nesnesi döndürürsünüz ), ancak onu tutmak istemezsiniz. .

Almak için ayırma / init'i çağırmadığınız bir nesne alırsanız - örneğin:

foo = [NSString stringWithString:@"hello"];

ancak bu nesneye bağlı kalmak istiyorsanız, [foo ret] çağırmanız gerekir. Aksi takdirde, bu mümkündür autoreleasedve sıfır referansı tutarsınız (yukarıdaki stringWithStringörnekte olduğu gibi ). Artık ihtiyacınız kalmadığında arayın [foo release].


2

Yukarıdaki cevaplar, dokümantasyonun ne söylediğine dair net açıklamalar verir; Yeni insanların çoğunun karşılaştığı sorun, belgelenmemiş vakalardır. Örneğin:

  • Otomatik yayın : Dokümanlar, bunun "gelecekte bir noktada" bir sürümü tetikleyeceğini söylüyor. NE ZAMAN?! Temel olarak, kodunuzdan sistem olay döngüsüne geri dönene kadar nesnenin etrafta olduğuna güvenebilirsiniz. Sistem, geçerli olay döngüsünden sonra herhangi bir zamanda nesneyi serbest bırakabilir. (Sanırım Matt bunu daha önce söyledi.)

  • Statik dizeler : NSString *foo = @"bar";- bunu saklamanız veya serbest bırakmanız gerekiyor mu? Hayır. Peki ya

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Oluşturma Kuralı : Eğer yarattıysanız, ona sahipsiniz ve serbest bırakmanız bekleniyor.

Genel olarak, yeni Kakao programcılarının karışmasının yolu, hangi rutinlerin bir nesneyi a ile döndürdüğünü anlamamaktır retainCount > 0.

İşte Cocoa'da Bellek Yönetimi İçin Çok Basit Kurallar'dan bir pasaj :

Saklama Sayımı kuralları

  • Belirli bir blok içinde, -copy, -alloc ve -retain kullanımı, -release ve -autorelease kullanımına eşit olmalıdır.
  • Kolaylık oluşturucular kullanılarak oluşturulan nesneler (örneğin, NSString'in stringWithString) otomatik olarak yayımlanmış kabul edilir.
  • Sahip olduğunuz örnek değişkenleri serbest bırakmak için bir -dealloc yöntemi uygulayın

1. madde şunu söylüyor: Eğer alloc(veya new fooCopy) aradıysanız, o nesnede release'i çağırmanız gerekir.

İkinci madde şunu söylüyor: Eğer bir kullanışlı kurucu kullanıyorsanız ve nesnenin etrafta dolaşması gerekiyorsa (daha sonra çizilecek bir görüntüde olduğu gibi), onu korumanız (ve daha sonra serbest bırakmanız) gerekir.

Üçüncüsü kendi kendini açıklayıcı olmalıdır.


"Otomatik yayın: dokümanlar, bunun gelecekte bir noktada bir sürümü tetikleyeceğini söylüyor." "NE ZAMAN ?!" Dokümanlar bu noktada nettir: "otomatik serbest bırakma yalnızca" daha sonra bir sürüm mesajı gönder "anlamına gelir (daha sonraki bir tanım için - bkz." Otomatik Yayın Havuzları ")." Tam olarak otomatik serbest bırakma havuzu yığınına bağlı olduğunda ...
mmalc

... "Sistem, mevcut olay döngüsünden sonra herhangi bir zamanda nesneyi serbest bırakabilir." Bu, sistemin göründüğünden daha az belirleyici görünmesini sağlar ...
mmalc

... NSString foo = [self getBar]; // tutmaya veya bırakmaya hala gerek yok Bu yanlış. GetBar'ı kim çağırırsa, uygulama ayrıntılarını bilmiyor, bu nedenle * , mevcut kapsamın dışında kullanmak istiyorlarsa (genellikle erişimciler aracılığıyla) saklamalı / bırakmalıdır.
mmalc

"Kakaodaki Bellek Yönetimi İçin Çok Basit Kurallar" makalesi birkaç bakımdan güncelliğini yitirmiştir - özellikle "Kolaylık oluşturucular kullanılarak oluşturulan nesneler (örn. NSString'in stringWithString) otomatik olarak yayımlanmış kabul edilir." doğru değil - basitçe "alıcıya ait değil".
mmalc


0

Birkaç kişinin daha önce de bahsettiği gibi, Apple'ın Bellek Yönetimine Giriş , başlamak için açık ara en iyi yerdir.

Henüz bahsetmediğim yararlı bir bağlantı, Pratik Bellek Yönetimi . Bunları okursanız, Apple'ın belgelerinin ortasında bulacaksınız, ancak doğrudan bağlanmaya değer. Hafıza yönetimi kurallarının örneklerle ve yaygın hatalarla (temelde buradaki diğer cevapların açıklamaya çalıştığı ama aynı zamanda değil) mükemmel bir yönetici özeti.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.