Memahami penghitungan referensi dengan Kakao dan Objective-C

122

Saya baru saja mulai melihat Objective-C dan Cocoa dengan maksud untuk bermain dengan iPhone SDK. Saya cukup nyaman dengan C mallocdan freekonsepnya, tetapi skema penghitungan referensi Cocoa membuat saya agak bingung. Saya diberitahu itu sangat elegan setelah Anda memahaminya, tetapi saya belum selesai.

Bagaimana release, retaindan autoreleasebekerja dan apa konvensi tentang penggunaannya?

(Atau jika gagal, apa yang Anda baca yang membantu Anda mendapatkannya?)

Matt Sheppard
sumber

Jawaban:

148

Mari kita mulai dengan retaindan release; autoreleasebenar-benar hanya kasus khusus setelah Anda memahami konsep dasarnya.

Di Cocoa, setiap objek melacak berapa kali itu direferensikan (khususnya, NSObjectkelas dasar mengimplementasikan ini). Dengan memanggil retainsuatu objek, Anda mengatakan kepadanya bahwa Anda ingin menambah jumlah referensinya satu per satu. Dengan memanggil release, Anda memberi tahu objek yang Anda lepaskan, dan jumlah referensinya berkurang. Jika, setelah memanggil release, jumlah referensi sekarang nol, maka memori objek tersebut dibebaskan oleh sistem.

Cara dasar ini berbeda dari mallocdanfree adalah bahwa objek tertentu tidak perlu khawatir tentang bagian lain dari sistem yang mogok karena Anda telah membebaskan memori yang mereka gunakan. Dengan asumsi semua orang bermain bersama dan mempertahankan / melepaskan sesuai dengan aturan, ketika satu bagian kode mempertahankan dan kemudian melepaskan objek, bagian kode lain yang juga mereferensikan objek tidak akan terpengaruh.

Apa yang terkadang membingungkan adalah mengetahui keadaan di mana Anda harus menelepon retaindan release. Aturan umum saya adalah bahwa jika saya ingin berpegang pada suatu objek untuk beberapa lama waktu (jika itu adalah variabel anggota di kelas, misalnya), maka saya perlu memastikan jumlah referensi objek tahu tentang saya. Seperti dijelaskan di atas, jumlah referensi objek bertambah dengan pemanggilan retain. Menurut konvensi, itu juga bertambah (disetel ke 1, sebenarnya) saat objek dibuat dengan metode "init". Dalam salah satu kasus ini, adalah tanggung jawab saya untuk memanggil releaseobjek tersebut setelah saya selesai. Jika tidak, akan ada kebocoran memori.

Contoh pembuatan objek:

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

Sekarang untuk autorelease. Pelepasan otomatis digunakan sebagai cara yang nyaman (dan terkadang perlu) untuk memberi tahu sistem agar membebaskan objek ini setelah beberapa saat. Dari perspektif perpipaan, saat autoreleasedipanggil, utas saat NSAutoreleasePoolini diberitahu tentang panggilan tersebut. The NSAutoreleasePoolsekarang tahu bahwa setelah mendapat kesempatan (setelah iterasi saat loop acara), dapat memanggil releasepada objek. Dari sudut pandang kami sebagai programmer, itu mengurus panggilan releaseuntuk kami, jadi kami tidak perlu (dan sebenarnya, kami tidak seharusnya).

Yang penting untuk diperhatikan adalah bahwa (sekali lagi, menurut konvensi) semua metode kelas pembuatan objek mengembalikan objek yang dirilis secara otomatis. Misalnya, dalam contoh berikut, variabel "s" memiliki jumlah referensi 1, tetapi setelah event loop selesai, variabel itu akan dimusnahkan.

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

Jika Anda ingin bergantung pada string itu, Anda harus memanggilnya retainsecara eksplisit, dan kemudian secara eksplisit releasesetelah selesai.

Pertimbangkan sedikit kode berikut (sangat dibuat-buat), dan Anda akan melihat situasi di mana autoreleasediperlukan:

- (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];
}

Saya menyadari semua ini agak membingungkan - pada titik tertentu, itu akan berbunyi klik. Berikut adalah beberapa referensi untuk membantu Anda:

  • Pengenalan Apple untuk manajemen memori.
  • Cocoa Programming for Mac OS X (4th Edition) , oleh Aaron Hillegas - sebuah buku yang ditulis dengan sangat baik dengan banyak contoh yang bagus. Bunyinya seperti tutorial.
  • Jika Anda benar-benar menyelam, Anda bisa menuju ke Peternakan Big Nerd . Ini adalah fasilitas pelatihan yang dijalankan oleh Aaron Hillegas - penulis buku yang disebutkan di atas. Saya menghadiri kursus Pengenalan Kakao di sana beberapa tahun yang lalu, dan itu adalah cara yang bagus untuk belajar.
Matt Dillard
sumber
8
Anda menulis: "Dengan memanggil pelepasan otomatis, kami meningkatkan sementara jumlah referensi". Saya pikir ini salah; autorelease hanya menandai objek yang akan dirilis di masa mendatang, itu tidak meningkatkan jumlah ref: cocoadev.com/index.pl?AutoRelease
LKM
2
"Sekarang untuk autorelease. Autorelease digunakan sebagai cara yang nyaman (dan terkadang perlu) untuk memberi tahu sistem agar membebaskan objek ini setelah beberapa saat." Sebagai kalimat awal, ini salah. Itu tidak memberitahu sistem untuk "membebaskan [itu], itu memberitahu itu untuk mengurangi jumlah yang ditahan.
mmalc
3
Terima kasih banyak atas penjelasannya yang bagus. Hanya satu hal yang masih belum jelas. Jika NSString* s = [[NSString alloc] initWithString:@"Hello World"];mengembalikan objek autoreleased (saat Anda menulisnya) mengapa saya harus melakukan return [s autorelease];dan menyetelnya "autorelease" lagi dan bukan hanya return s?
znq
3
@ Stefan: [[NSString alloc] initWithString:@"Hello World"]TIDAK akan mengembalikan objek autoreleased. Kapan allocpun dipanggil, jumlah referensi disetel ke 1, dan kode tersebut bertanggung jawab untuk memastikannya dirilis. The [NSString stringWithString:]call, di sisi lain, tidak mengembalikan objek autoreleased.
Matt Dillard
6
Hal sepele yang menyenangkan: Karena jawabannya menggunakan @ "" dan NSString, stringnya konstan dan, dengan demikian, jumlah retensi absolut akan konstan dan sama sekali tidak relevan .... tidak membuat jawaban salah, dengan cara apa pun, hanya memperkuat fakta bahwa jumlah retensi absolut bukanlah sesuatu yang harus Anda khawatirkan.
bbum
10

Jika Anda memahami proses penyimpanan / pelepasan maka ada dua aturan emas yang "duh" jelas bagi pemrogram Kakao yang sudah mapan, namun sayangnya hal ini jarang dijabarkan dengan jelas untuk pendatang baru.

  1. Jika fungsi yang mengembalikan objek memiliki alloc , createatau copydalam namanya, maka objek tersebut menjadi milik Anda. Anda harus menelepon [object release]setelah Anda selesai melakukannya. Atau CFRelease(object), jika itu adalah objek Core-Foundation.

  2. Jika TIDAK memiliki salah satu dari kata-kata ini dalam namanya, maka objek tersebut milik orang lain. Anda harus memanggil [object retain]jika Anda ingin menyimpan objek setelah fungsi Anda berakhir.

Anda akan senang juga mengikuti konvensi ini dalam fungsi yang Anda buat sendiri.

(Nitpickers: Ya, sayangnya ada beberapa panggilan API yang merupakan pengecualian dari aturan ini tetapi jarang terjadi).

Andrew Grant
sumber
11
Ini tidak lengkap dan tidak akurat. Saya terus gagal memahami mengapa orang mencoba mengulangi aturan daripada hanya menunjuk ke dokumentasi yang relevan: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc
4
Aturan Yayasan Inti secara khusus berbeda dari aturan Cocoa; lihat developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc
1
Saya juga tidak setuju. Jika suatu fungsi mengembalikan sesuatu yang tidak ingin dimilikinya, ia harus melepaskannya secara otomatis. Ini adalah pemanggil dari tugas fungsi untuk mempertahankannya (jika diinginkan). Seharusnya TIDAK ADA hubungannya dengan nama metode apa pun yang dipanggil. Itu lebih merupakan pengkodean Gaya C di mana kepemilikan objek tidak jelas.
Sam
1
Maaf! Saya pikir saya terburu-buru dalam memilih down-voting. Aturan Manajemen Memori Jawaban Anda hampir mengutip dokumen apple.
Sam
8

Jika Anda menulis kode untuk desktop dan Anda dapat menargetkan Mac OS X 10.5, Anda setidaknya harus menggunakan pengumpulan sampah Objective-C. Ini benar-benar akan menyederhanakan sebagian besar pengembangan Anda - itulah mengapa Apple berupaya semaksimal mungkin untuk membuatnya, dan membuatnya bekerja dengan baik.

Adapun aturan manajemen memori saat tidak menggunakan GC:

  • Jika Anda membuat objek baru menggunakan +alloc/+allocWithZone:, +new, -copyatau -mutableCopyatau jika Anda -retainobjek, Anda mengambil kepemilikan itu dan harus memastikan itu dikirim -release.
  • Jika Anda menerima suatu objek dengan cara lain, Anda bukan pemiliknya dan tidak boleh memastikannya dikirim -release.
  • Jika Anda ingin memastikan sebuah objek dikirim, -releaseAnda dapat mengirimnya sendiri, atau Anda dapat mengirim objek tersebut -autoreleasedan kumpulan rilis otomatis saat ini akan mengirimkannya -release(sekali per diterima -autorelease) ketika kumpulan tersebut dikuras.

Biasanya -autoreleasedigunakan sebagai cara untuk memastikan bahwa objek hidup selama acara saat ini, tetapi dibersihkan setelahnya, karena ada kumpulan rilis otomatis yang mengelilingi pemrosesan acara Cocoa. Di Cocoa, jauh lebih umum untuk mengembalikan objek ke pemanggil yang dirilis secara otomatis daripada mengembalikan objek yang perlu dilepaskan oleh pemanggil itu sendiri.

Chris Hanson
sumber
6

Objective-C menggunakan Reference Counting , yang berarti setiap Objek memiliki jumlah referensi. Saat sebuah objek dibuat, ia memiliki jumlah referensi "1". Sederhananya, ketika sebuah objek dirujuk (yaitu, disimpan di suatu tempat), ia "dipertahankan" yang berarti jumlah referensinya bertambah satu. Ketika sebuah objek tidak lagi dibutuhkan, itu "dilepaskan" yang berarti jumlah referensinya berkurang satu.

Saat jumlah referensi objek adalah 0, objek tersebut dibebaskan. Ini adalah penghitungan referensi dasar.

Untuk beberapa bahasa, referensi otomatis bertambah dan berkurang, tetapi tujuan-c bukan salah satu bahasa tersebut. Dengan demikian pemrogram bertanggung jawab untuk menyimpan dan melepaskan.

Cara umum untuk menulis metode adalah:

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

Masalah keharusan mengingat untuk melepaskan sumber daya yang diperoleh di dalam kode adalah hal yang membosankan dan rawan kesalahan. Objective-C memperkenalkan konsep lain yang bertujuan untuk membuat ini lebih mudah: Kumpulan Autorelease. Kumpulan rilis otomatis adalah objek khusus yang diinstal di setiap utas. Mereka adalah kelas yang cukup sederhana, jika Anda mencari NSAutoreleasePool.

Saat sebuah objek mendapatkan pesan "autorelease" yang dikirim ke sana, objek tersebut akan mencari kumpulan autorelease apa pun yang ada di tumpukan untuk utas saat ini. Ini akan menambahkan objek ke daftar sebagai objek untuk mengirim pesan "rilis" ke beberapa titik di masa mendatang, yang biasanya terjadi ketika kumpulan itu sendiri dilepaskan.

Mengambil kode di atas, kamu bisa menulis ulang menjadi lebih pendek dan mudah dibaca dengan mengatakan:

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

Karena objek tersebut dirilis otomatis, kita tidak perlu lagi secara eksplisit memanggil "rilis" di atasnya. Ini karena kita tahu beberapa kumpulan rilis otomatis akan melakukannya untuk kita nanti.

Semoga ini membantu. Artikel Wikipedia cukup bagus tentang penghitungan referensi. Informasi lebih lanjut tentang kumpulan rilis otomatis dapat ditemukan di sini . Perhatikan juga bahwa jika Anda membuat untuk Mac OS X 10.5 dan yang lebih baru, Anda dapat memberi tahu Xcode untuk membangun dengan pengumpulan sampah diaktifkan, memungkinkan Anda untuk sepenuhnya mengabaikan pertahankan / rilis / rilis otomatis.

NilObject
sumber
2
Ini salah. Tidak perlu mengirim rilis someObject atau autorlease dalam salah satu contoh yang ditampilkan.
mmalc
6

Joshua (# 6591) - Koleksi Sampah di Mac OS X 10.5 tampaknya cukup keren, tetapi tidak tersedia untuk iPhone (atau jika Anda ingin aplikasi Anda berjalan di Mac OS X versi sebelum 10.5).

Selain itu, jika Anda sedang menulis perpustakaan atau sesuatu yang mungkin digunakan kembali, menggunakan mode GC mengunci siapa pun yang menggunakan kode ke juga menggunakan mode GC, jadi seperti yang saya pahami, siapa pun yang mencoba menulis kode yang dapat digunakan kembali secara luas cenderung pergi untuk mengelola memori secara manual.

Matt Sheppard
sumber
2
Sangat mungkin untuk menulis framework hybrid yang mendukung penghitungan GC dan referensi.
mmalc
6

Seperti biasa, ketika orang mulai mencoba menata ulang materi referensi, mereka hampir selalu mendapatkan kesalahan atau memberikan deskripsi yang tidak lengkap.

Apple memberikan penjelasan lengkap tentang sistem manajemen memori Cocoa dalam Panduan Pemrograman Manajemen Memori untuk Cocoa , di bagian akhir terdapat ringkasan singkat namun akurat tentang Aturan Manajemen Memori .

mmalc
sumber
1
Dan untuk aturan ringkasan: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
Michael Baltaks
2
Sebenarnya ini adalah ringkasan satu halaman yang jauh lebih baik: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau
6

Saya tidak akan menambahkan spesifik retensi / rilis selain dari yang mungkin Anda pikirkan tentang menjatuhkan $ 50 dan mendapatkan buku Hillegass, tetapi saya sangat menyarankan untuk menggunakan alat Instrumen sejak awal dalam pengembangan aplikasi Anda (bahkan pertama!). Untuk melakukannya, Jalankan-> Mulai dengan alat kinerja. Saya akan mulai dengan Leaks yang hanya salah satu dari banyak instrumen yang tersedia tetapi akan membantu menunjukkan kepada Anda ketika Anda lupa merilisnya. Tidaklah menakutkan berapa banyak informasi yang akan Anda sajikan. Tapi lihat tutorial ini untuk bangun dan bekerja dengan cepat:
TUTORIAL KAKAO: MEMPERBAIKI KEBOCORAN MEMORI DENGAN INSTRUMEN

Sebenarnya mencoba untuk memaksa kebocoran mungkin merupakan cara yang lebih baik, pada gilirannya, belajar bagaimana mencegahnya! Semoga berhasil ;)

rampok
sumber
5

Matt Dillard menulis :

return [rilis autorelease]];

Autorelease tidak menyimpan objek tersebut. Autorelease hanya menempatkannya dalam antrian untuk dirilis nanti. Anda tidak ingin memiliki pernyataan rilis di sana.

NilObject
sumber
4

Ada screencast gratis yang tersedia dari iDeveloperTV Network

Manajemen Memori di Objective-C

Abizern
sumber
1
Sayangnya tautan ini sekarang menjadi 404.
Ari Braginsky
4

Jawaban NilObject adalah awal yang baik. Berikut beberapa info tambahan yang berkaitan dengan manajemen memori manual ( diperlukan di iPhone ).

Jika Anda sendiri adalah alloc/initsebuah benda, ia datang dengan hitungan referensi 1. Anda bertanggung jawab untuk membersihkannya setelah benda itu tidak lagi dibutuhkan, baik dengan menelepon [foo release]atau [foo autorelease]. release segera membersihkannya, sedangkan autorelease menambahkan objek ke kumpulan autorelease, yang akan secara otomatis melepaskannya di lain waktu.

autorelease terutama untuk saat Anda memiliki metode yang perlu mengembalikan objek yang dipermasalahkan ( jadi Anda tidak bisa melepaskannya secara manual, kalau tidak Anda akan mengembalikan objek nil ) tetapi Anda juga tidak ingin menahannya, .

Jika Anda memperoleh objek di mana Anda tidak memanggil alokasi / init untuk mendapatkannya - misalnya:

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

tetapi Anda ingin berpegang pada objek ini, Anda perlu memanggil [foo dipertahankan]. Jika tidak, itu mungkin akan didapat autoreleaseddan Anda akan berpegang pada referensi nihil (seperti pada stringWithStringcontoh di atas ). Jika Anda tidak lagi membutuhkannya, hubungi [foo release].

Mike McMaster
sumber
2

Jawaban di atas memberikan pernyataan kembali yang jelas tentang apa yang dikatakan dokumentasi; masalah yang dihadapi kebanyakan orang baru adalah kasus-kasus yang tidak berdokumen. Sebagai contoh:

  • Autorelease : menurut dokumen ini akan memicu rilis "di beberapa titik di masa mendatang". KAPAN?! Pada dasarnya, Anda dapat mengandalkan objek yang ada di sekitar hingga Anda keluar dari kode Anda kembali ke loop peristiwa sistem. Sistem DAPAT melepaskan objek kapan saja setelah siklus kejadian saat ini. (Saya pikir Matt mengatakan itu, sebelumnya.)

  • String statis : NSString *foo = @"bar";- apakah Anda harus menyimpan atau melepaskannya? Tidak. Bagaimana

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

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Aturan Penciptaan : Jika Anda membuatnya, Anda adalah pemiliknya, dan diharapkan untuk merilisnya.

Secara umum, cara pemrogram Cocoa baru menjadi kacau adalah dengan tidak memahami rutinitas mana yang mengembalikan objek dengan a retainCount > 0.

Berikut ini potongan dari Very Simple Rules For Memory Management In Cocoa :

Aturan Hitungan Retensi

  • Dalam blok tertentu, penggunaan -copy, -alloc dan -retain harus sama dengan penggunaan -release dan -autorelease.
  • Objek yang dibuat menggunakan konstruktor praktis (misalnya stringWithString NSString) dianggap rilis otomatis.
  • Menerapkan metode -dealloc untuk merilis variabel instance yang Anda miliki

Poin pertama mengatakan: jika Anda memanggil alloc(atau new fooCopy), Anda perlu memanggil rilis pada objek itu.

Poin ke-2 mengatakan: jika Anda menggunakan konstruktor kenyamanan dan Anda membutuhkan objek untuk berkeliaran (seperti dengan gambar yang akan digambar nanti), Anda perlu mempertahankan (dan kemudian melepaskannya).

Yang ketiga harus cukup jelas.

Olie
sumber
"Autorelease: dokumen mengatakan itu akan memicu rilis" di beberapa titik di masa mendatang. "KAPAN ?!" Dokumennya menjelaskan hal itu dengan jelas: "autorelease berarti" kirim pesan rilis nanti "(untuk beberapa definisi nanti — lihat" Kumpulan Pelepasan Otomatis ")." Tepatnya ketika tergantung pada tumpukan
kumpulan rilis otomatis
... "Sistem DAPAT melepaskan objek kapan saja setelah siklus peristiwa saat ini." Ini membuat sistem terdengar kurang deterministik daripada ...
mmalc
... NSString foo = [self getBar]; // masih tidak perlu menyimpan atau melepaskan Ini salah. Siapa pun yang memanggil getBar tidak mengetahui detail implementasinya, jadi * harus mempertahankan / melepaskan (biasanya melalui aksesor) jika mereka ingin menggunakannya di luar cakupan saat ini.
mmalc
Artikel "Aturan Sangat Sederhana Untuk Manajemen Memori Dalam Kakao" dalam beberapa hal telah kedaluwarsa - khususnya "Objek yang dibuat menggunakan konstruktor praktis (misalnya stringWithString NSString) dianggap dirilis otomatis." tidak benar - ini hanya "tidak dimiliki oleh penerima".
mmalc
0

Seperti yang telah disebutkan beberapa orang, Pengenalan Manajemen Memori Apple sejauh ini adalah tempat terbaik untuk memulai.

Satu tautan berguna yang belum saya lihat sebutkan adalah Manajemen Memori Praktis . Anda akan menemukannya di tengah-tengah dokumen Apple jika Anda membacanya, tetapi perlu ditautkan secara langsung. Ini adalah ringkasan eksekutif brilian dari aturan manajemen memori dengan contoh dan kesalahan umum (pada dasarnya apa yang coba dijelaskan oleh jawaban lain di sini, tetapi tidak juga).

Brian Moeskau
sumber