Mengapa @autoreleasepool masih diperlukan dengan ARC?

191

Sebagian besar dengan ARC (Penghitungan Referensi Otomatis), kita tidak perlu memikirkan manajemen memori sama sekali dengan objek Objective-C. Tidak diizinkan membuat NSAutoreleasePoollagi, namun ada sintaks baru:

@autoreleasepool {
    
}

Pertanyaan saya adalah, mengapa saya membutuhkan ini ketika saya tidak seharusnya merilis / autoreleasing secara manual?


EDIT: Untuk meringkas apa yang saya dapatkan dari semua jawaban dan komentar dengan singkat:

Sintaks Baru:

@autoreleasepool { … } adalah sintaks baru untuk

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

Lebih penting:

  • ARC menggunakan autoreleaseserta release.
  • Dibutuhkan kolam rilis otomatis untuk melakukannya.
  • ARC tidak membuat kumpulan rilis otomatis untuk Anda. Namun:
    • Utas utama dari setiap aplikasi Kakao sudah memiliki kumpulan autorelease di dalamnya.
  • Ada dua kesempatan di mana Anda mungkin ingin memanfaatkan @autoreleasepool:
    1. Ketika Anda berada di utas sekunder dan tidak ada kumpulan pelepasan otomatis, Anda harus membuatnya sendiri untuk mencegah kebocoran, seperti myRunLoop(…) { @autoreleasepool { … } return success; } .
    2. Ketika Anda ingin membuat kolam yang lebih lokal, seperti @mattjgalloway telah ditampilkan dalam jawabannya.
mk12
sumber
1
Ada juga kesempatan ketiga: Ketika Anda mengembangkan sesuatu yang tidak terkait dengan UIKit atau NSFoundation. Sesuatu yang menggunakan alat-alat baris perintah atau lebih
Garnik

Jawaban:

214

ARC tidak menghilangkan retensi, rilis, dan autorelease, ARC hanya menambahkan yang diperlukan untuk Anda. Jadi masih ada panggilan untuk mempertahankan, masih ada panggilan untuk melepaskan, masih ada panggilan untuk autorelease dan masih ada kolam rilis otomatis.

Salah satu perubahan lain yang mereka buat dengan compiler Clang 3.0 dan ARC yang baru adalah bahwa mereka diganti NSAutoReleasePooldengan @autoreleasepooldirektif kompiler. NSAutoReleasePoolselalu sedikit "objek" khusus dan mereka membuatnya sehingga sintaks menggunakan satu tidak bingung dengan objek sehingga umumnya sedikit lebih sederhana.

Jadi pada dasarnya, Anda perlu @autoreleasepoolkarena masih ada kolam rilis otomatis yang perlu dikhawatirkan. Anda tidak perlu khawatir untuk menambahkanautorelease panggilan.

Contoh menggunakan kumpulan rilis otomatis:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Contoh yang sangat dibuat-buat, tentu saja, tetapi jika Anda tidak memiliki bagian @autoreleasepooldalam for-loop luar maka Anda akan melepaskan 10.000.000 objek kemudian pada bukan 10.000 setiap kali putaran bagian luar for-loop.

Pembaruan: Lihat juga jawaban ini - https://stackoverflow.com/a/7950636/1068248 - untuk mengapa @autoreleasepooltidak ada hubungannya dengan ARC.

Pembaruan: Saya melihat bagian dalam dari apa yang terjadi di sini dan menulisnya di blog saya . Jika Anda melihat di sana maka Anda akan melihat apa yang dilakukan ARC dan bagaimana gaya baru @autoreleasepooldan bagaimana memperkenalkan lingkup digunakan oleh kompiler untuk menyimpulkan informasi tentang apa yang diperlukan, rilis & autorelease yang diperlukan.

mattjgalloway
sumber
11
Itu tidak menghilangkan sisa. Itu menambahkan mereka untuk Anda. Penghitungan referensi masih berjalan, hanya saja otomatis. Maka Menghitung Referensi Otomatis :-D.
mattjgalloway
6
Jadi mengapa itu tidak menambah @autoreleasepoolbagi saya juga? Jika saya tidak mengendalikan apa yang akan autoreleased atau dirilis (ARC melakukan itu untuk saya), bagaimana saya harus tahu kapan harus menyiapkan kumpulan autorelease?
mk12
5
Tetapi Anda memiliki kendali atas ke mana kolam rilis otomatis Anda diam. Ada satu yang melilit seluruh aplikasi Anda secara default, tetapi Anda mungkin menginginkan lebih.
mattjgalloway
5
Pertanyaan bagus. Anda hanya perlu "tahu". Pikirkan menambahkan satu sama dengan mengapa seseorang mungkin, dalam bahasa GC, menambahkan petunjuk kepada pemulung untuk terus maju dan menjalankan siklus pengumpulan sekarang. Mungkin Anda tahu ada banyak objek yang siap dihapus, Anda memiliki loop yang mengalokasikan banyak objek temp, sehingga Anda "tahu" (atau Instrumen mungkin memberi tahu Anda :) bahwa menambahkan kumpulan rilis di sekitar loop akan menjadi ide bagus.
Graham Perks
6
Contoh looping berfungsi dengan baik tanpa autorelease: setiap objek dideallocated ketika variabel keluar dari ruang lingkup. Menjalankan kode tanpa autorelease membutuhkan jumlah memori yang konstan dan menunjukkan pointer digunakan kembali, dan menempatkan breakpoint pada dealloc suatu objek menunjukkannya dipanggil sekali setiap kali melalui loop, ketika objc_storeStrong dipanggil. Mungkin OSX melakukan sesuatu yang bodoh di sini, tetapi autoreleasepool sama sekali tidak perlu di iOS.
Glenn Maynard
16

@autoreleasepooltidak melepaskan otomatis apa pun. Ini menciptakan kumpulan autorelease, sehingga ketika akhir blok tercapai, objek apa pun yang autorelease oleh ARC saat blok aktif akan dikirim pesan rilis. Panduan Pemrograman Manajemen Memori Tingkat Lanjut Apple menjelaskannya sebagai berikut:

Di akhir blok kumpulan autorelease, objek yang menerima pesan autorelease di dalam blok dikirimi pesan pelepasan — objek menerima pesan rilis untuk setiap kali dikirim pesan autorelease di dalam blok.

outis
sumber
1
Belum tentu. Objek akan menerima releasepesan tetapi jika jumlah tetap> 1 objek TIDAK akan dibatalkan alokasi.
andybons
@andybons: diperbarui; Terima kasih. Apakah ini perubahan dari perilaku pra-ARC?
outis
Ini salah. Objek yang dirilis oleh ARC akan dikirimkan pesan rilis segera setelah dirilis oleh ARC, dengan atau tanpa kumpulan autorelease.
Glenn Maynard
7

Orang sering salah paham tentang ARC untuk pengumpulan sampah atau sejenisnya. Yang benar adalah, setelah beberapa waktu orang-orang di Apple (berkat proyek llvm dan dentang) menyadari bahwa administrasi memori Objective-C (semua retainsdan releases, dll.) Dapat diotomatisasi sepenuhnya pada waktu kompilasi . Ini, hanya dengan membaca kode, bahkan sebelum dijalankan! :)

Untuk melakukannya hanya ada satu syarat: Kami HARUS mengikuti aturan , jika tidak, kompiler tidak akan dapat mengotomatiskan proses pada waktu kompilasi. Jadi, untuk memastikan bahwa kita tidak pernah melanggar aturan, kami tidak diperbolehkan secara eksplisit menulis release, retain, dll Mereka panggilan secara otomatis disuntikkan ke dalam kode kami oleh compiler. Oleh karena itu internal kita masih memiliki autoreleases, retain, release, dll Hal ini hanya kita tidak perlu menulis mereka lagi.

A of ARC otomatis pada waktu kompilasi, yang jauh lebih baik daripada saat run time seperti pengumpulan sampah.

Kami masih memilikinya @autoreleasepool{...}karena tidak melanggar aturan apa pun, kami bebas membuat / menguras kumpulan kami kapan saja kami membutuhkannya :).

nacho4d
sumber
1
ARC adalah referensi penghitungan GC, bukan mark-and-sweep GC seperti yang Anda dapatkan di JavaScript dan Java, tapi ini pasti pengumpulan sampah. Ini tidak menjawab pertanyaan - "Anda bisa" tidak menjawab pertanyaan "mengapa Anda harus". Anda seharusnya tidak.
Glenn Maynard
3

Itu karena Anda masih perlu memberikan kompiler dengan petunjuk tentang kapan aman bagi objek autoreleased untuk keluar dari ruang lingkup.

DougW
sumber
Bisakah Anda memberi saya contoh kapan Anda perlu melakukan ini?
mk12
Jadi sebelum ARC, misalnya, saya memiliki CVDisplayLink yang berjalan di utas sekunder untuk aplikasi OpenGL saya, tetapi saya tidak membuat kumpulan autorelease di runloopnya karena saya tahu saya tidak autoraleasing apa pun (atau menggunakan perpustakaan yang melakukannya). Apakah itu berarti sekarang saya perlu menambahkan @autoreleasepoolkarena saya tidak tahu apakah ARC mungkin memutuskan untuk melepaskan sesuatu secara otomatis?
mk12
@ Mk12 - Tidak. Anda akan selalu memiliki kumpulan rilis otomatis yang dikuras setiap kali sepanjang putaran run utama. Anda hanya perlu menambahkan satu ketika Anda ingin memastikan bahwa objek yang telah autoreleased dikeringkan sebelum mereka sebaliknya - misalnya, kali berikutnya putaran run loop.
mattjgalloway
2
@DougW - Saya melihat apa yang sebenarnya dilakukan oleh kompiler dan membuat blog tentang hal ini di sini - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Semoga menjelaskan apa yang terjadi di kedua waktu kompilasi dan waktu berjalan.
mattjgalloway
2

Dikutip dari https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Blok dan Thread Kolam Autorelease

Setiap utas dalam aplikasi Kakao memelihara tumpukan blok pool autorelease sendiri. Jika Anda menulis program khusus Yayasan atau jika Anda melepaskan utas, Anda perlu membuat blok pool autorelease sendiri.

Jika aplikasi atau utas Anda berumur panjang dan berpotensi menghasilkan banyak objek autoreleased, Anda harus menggunakan blok pool autorelease (seperti AppKit dan UIKit lakukan pada utas utama); jika tidak, objek autoreleased menumpuk dan jejak memori Anda tumbuh. Jika utas terlepas Anda tidak melakukan panggilan Kakao, Anda tidak perlu menggunakan blok kumpulan autorelease.

Catatan: Jika Anda membuat utas sekunder menggunakan API utas POSIX alih-alih NSThread, Anda tidak dapat menggunakan Kakao kecuali Kakao dalam mode multithreading. Kakao memasuki mode multithreading hanya setelah melepaskan objek NSThread pertamanya. Untuk menggunakan Kakao pada utas POSIX sekunder, aplikasi Anda harus terlebih dahulu melepaskan setidaknya satu objek NSThread, yang dapat segera keluar. Anda dapat menguji apakah Kakao dalam mode multithreading dengan metode kelas NSThread isMultiThreaded.

...

Dalam Penghitungan Referensi Otomatis, atau ARC, sistem menggunakan sistem penghitungan referensi yang sama dengan MRR, tetapi ia memasukkan metode manajemen memori yang sesuai untuk Anda pada waktu kompilasi. Anda sangat dianjurkan untuk menggunakan ARC untuk proyek baru. Jika Anda menggunakan ARC, biasanya tidak perlu memahami implementasi yang mendasari yang dijelaskan dalam dokumen ini, meskipun dalam beberapa situasi mungkin bermanfaat. Untuk informasi lebih lanjut tentang ARC, lihat Transisi ke ARC Release Notes.

Raunak
sumber
2

Diperlukan kumpulan autorelease untuk mengembalikan objek yang baru dibuat dari suatu metode. Misalnya pertimbangkan potongan kode ini:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

String yang dibuat dalam metode akan memiliki jumlah tetap satu. Sekarang siapa yang akan menyeimbangkan jumlah tetap itu dengan rilis?

Metodenya sendiri? Tidak mungkin, harus mengembalikan objek yang dibuat, jadi tidak boleh melepaskannya sebelum kembali.

Penelepon metode ini? Penelepon tidak berharap untuk mengambil objek yang perlu dirilis, nama metode tidak menyiratkan bahwa objek baru dibuat, ia hanya mengatakan bahwa suatu objek dikembalikan dan objek yang dikembalikan ini mungkin yang baru yang membutuhkan rilis tetapi mungkin sebagai baik menjadi yang sudah ada yang tidak. Apa metode yang dikembalikan mungkin bahkan tergantung pada beberapa keadaan internal, sehingga penelepon tidak bisa tahu apakah harus melepaskan objek itu dan tidak harus peduli.

Jika penelepon harus selalu melepaskan semua objek yang dikembalikan oleh konvensi, maka setiap objek yang tidak baru dibuat harus selalu disimpan sebelum mengembalikannya dari suatu metode dan itu harus dilepaskan oleh penelepon setelah keluar dari ruang lingkup, kecuali dikembalikan lagi. Ini akan menjadi sangat tidak efisien dalam banyak kasus karena seseorang dapat sepenuhnya menghindari mengubah mempertahankan jumlah dalam banyak kasus jika penelepon tidak akan selalu melepaskan objek yang dikembalikan.

Itu sebabnya ada kolam autorelease, jadi metode pertama sebenarnya akan menjadi

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Memanggil autoreleaseobjek menambahkannya ke kolam autorelease, tetapi apa artinya itu, menambahkan objek ke kolam autorelease? Ya, itu berarti memberi tahu sistem Anda, " Saya ingin Anda melepaskan objek itu untuk saya tetapi beberapa waktu kemudian, tidak sekarang; ia memiliki jumlah penahanan yang perlu diseimbangkan dengan rilis, jika tidak memori akan bocor tetapi saya tidak bisa melakukannya sendiri sekarang, karena saya perlu objek untuk tetap hidup di luar jangkauan saya saat ini dan penelepon saya tidak akan melakukannya untuk saya juga, itu tidak memiliki pengetahuan bahwa ini perlu dilakukan. Jadi tambahkan ke kolam Anda dan setelah Anda membersihkan itu kolam renang, juga membersihkan objek saya untuk saya. "

Dengan ARC, kompiler memutuskan kapan Anda akan menyimpan objek, kapan melepaskan objek, dan kapan menambahkannya ke kumpulan autorelease tetapi masih membutuhkan kehadiran kumpulan autorelease untuk dapat mengembalikan objek yang baru dibuat dari metode tanpa kebocoran memori. Apple baru saja membuat beberapa optimasi bagus untuk kode yang dihasilkan yang kadang-kadang akan menghilangkan kolam autorelease selama runtime. Optimalisasi ini mensyaratkan bahwa keduanya, penelepon dan callee menggunakan ARC (ingat mencampur ARC dan non-ARC adalah legal dan juga didukung secara resmi) dan jika itu sebenarnya kasusnya hanya dapat diketahui saat runtime.

Pertimbangkan Kode ARC ini:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Kode yang dihasilkan oleh sistem, dapat berperilaku seperti kode berikut (yaitu versi aman yang memungkinkan Anda untuk secara bebas mencampur kode ARC dan non-ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Perhatikan bahwa retain / release dalam pemanggil hanya mempertahankan keamanan defensif, itu tidak sepenuhnya diperlukan, kode akan benar tanpa itu)

Atau bisa berperilaku seperti kode ini, jika keduanya terdeteksi menggunakan ARC saat runtime:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

Seperti yang Anda lihat, Apple menghilangkan atuorelease, dengan demikian juga pelepasan objek tertunda ketika kolam hancur, serta keselamatan tetap. Untuk mempelajari lebih lanjut tentang bagaimana itu mungkin dan apa yang sebenarnya terjadi di balik layar, lihat posting blog ini.

Sekarang untuk pertanyaan aktual: Mengapa orang menggunakannya @autoreleasepool ?

Bagi sebagian besar pengembang, hanya ada satu alasan yang tersisa hari ini untuk menggunakan konstruksi ini dalam kode mereka dan itu adalah untuk menjaga jejak memori kecil di mana berlaku. Misalnya pertimbangkan loop ini:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Asumsikan bahwa setiap panggilan tempObjectForDatadapat membuat yang baruTempObject yang dikembalikan autorelease. For-loop akan membuat satu juta objek temp ini yang semuanya dikumpulkan dalam autoreleasepool saat ini dan hanya sekali kolam itu dihancurkan, semua objek temp dihancurkan juga. Sampai itu terjadi, Anda memiliki satu juta objek temp ini dalam memori.

Jika Anda menulis kode seperti ini sebagai gantinya:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Kemudian kumpulan baru dibuat setiap kali for-loop berjalan dan dihancurkan pada akhir setiap iterasi loop. Dengan cara itu paling banyak satu objek temp sedang nongkrong di memori setiap saat meskipun loop berjalan satu juta kali.

Di masa lalu, Anda sering harus mengelola sendiri autoreleasepools saat mengelola utas (misalnya menggunakan NSThread) karena hanya utas utama yang secara otomatis memiliki kumpulan autorelease untuk aplikasi Cocoa / UIKit. Namun ini cukup banyak warisan hari ini karena hari ini Anda mungkin tidak akan menggunakan utas untuk memulai. Anda akan menggunakan GCD DispatchQueueatau atau NSOperationQueuekeduanya dan keduanya mengelola kumpulan autorelease tingkat atas untuk Anda, dibuat sebelum menjalankan blok / tugas dan dihancurkan setelah selesai dengan itu.

Mecki
sumber
-4

Tampaknya ada banyak kebingungan tentang topik ini (dan setidaknya 80 orang yang mungkin sekarang bingung tentang hal ini dan berpikir mereka perlu menaburkan @autoreleasepool di sekitar kode mereka).

Jika sebuah proyek (termasuk dependensinya) secara eksklusif menggunakan ARC, maka @autoreleasepool tidak perlu digunakan dan tidak akan melakukan apa pun yang berguna. ARC akan menangani pelepasan objek pada waktu yang tepat. Sebagai contoh:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

menampilkan:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Setiap objek Pengujian dibatalkan alokasi segera setelah nilai keluar dari ruang lingkup, tanpa menunggu kumpulan autorelease untuk keluar. (Hal yang sama terjadi dengan contoh NSNumber; ini hanya memungkinkan kita mengamati dealloc.) ARC tidak menggunakan autorelease.

Alasan @autoreleasepool masih diizinkan adalah untuk proyek ARC campuran dan non-ARC, yang belum sepenuhnya dialihkan ke ARC.

Jika Anda memanggil kode non-ARC, itu dapat mengembalikan objek autoreleased. Jika demikian, loop di atas akan bocor, karena kumpulan autorelease saat ini tidak akan pernah keluar. Di situlah Anda ingin meletakkan @autoreleasepool di sekitar blok kode.

Tetapi jika Anda telah sepenuhnya membuat transisi ARC, lupakan tentang autoreleasepool.

Glenn Maynard
sumber
4
Jawaban ini salah dan juga bertentangan dengan dokumentasi ARC. bukti Anda adalah anekdotal karena Anda kebetulan menggunakan metode alokasi yang kompiler memutuskan untuk tidak autorelease. Anda dapat dengan mudah melihat ini tidak berfungsi jika Anda membuat penginisialisasi statis baru untuk kelas kustom Anda. Buat initializer ini dan menggunakannya dalam loop Anda: + (Testing *) testing { return [Testing new] }. Maka Anda akan melihat bahwa dealloc tidak akan dipanggil sampai nanti. Ini diperbaiki jika Anda membungkus bagian dalam loop di @autoreleasepoolblok.
Dima
@Dima Mencoba di iOS10, dealloc dipanggil segera setelah mencetak alamat objek. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC
@ KudoCC - Aku juga, dan aku melihat perilaku yang sama yang kamu lakukan. Tetapi, ketika saya melemparkan [UIImage imageWithData]ke dalam persamaan, kemudian, tiba-tiba, saya mulai melihat autoreleaseperilaku tradisional , yang membutuhkan @autoreleasepooluntuk menjaga memori puncak ke tingkat yang masuk akal.
Rob
@Rob Saya tidak bisa menahan diri untuk menambahkan tautan .
KudoCC