Jawaban singkat
Alih-alih mengakses self
secara langsung, Anda harus mengaksesnya secara tidak langsung, dari referensi yang tidak akan disimpan. Jika Anda tidak menggunakan Penghitungan Referensi Otomatis (ARC) , Anda dapat melakukan ini:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Kata __block
kunci menandai variabel yang dapat dimodifikasi di dalam blok (kami tidak melakukan itu) tetapi juga mereka tidak secara otomatis dipertahankan ketika blok tersebut dipertahankan (kecuali Anda menggunakan ARC). Jika Anda melakukan ini, Anda harus yakin bahwa tidak ada lagi yang akan mencoba untuk menjalankan blok setelah instance MyDataProcessor dirilis. (Mengingat struktur kode Anda, itu seharusnya tidak menjadi masalah.) Baca lebih lanjut tentang__block
.
Jika Anda menggunakan ARC , semantik __block
perubahan dan referensi akan dipertahankan, dalam hal ini Anda harus mendeklarasikannya __weak
.
Jawaban panjang
Katakanlah Anda memiliki kode seperti ini:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Masalahnya di sini adalah diri mempertahankan referensi ke blok; sementara itu blok harus mempertahankan referensi ke diri sendiri untuk mengambil properti delegasi dan mengirim metode delegasi. Jika semua hal lain di aplikasi Anda melepaskan rujukannya ke objek ini, jumlah tetapnya tidak akan nol (karena blok mengarah ke sana) dan blok tidak melakukan kesalahan (karena objek menunjuk ke sana) dan seterusnya sepasang objek akan bocor ke tumpukan, menempati memori tetapi selamanya tidak dapat dijangkau tanpa debugger. Tragis, sungguh.
Kasing itu bisa dengan mudah diperbaiki dengan melakukan ini sebagai gantinya:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
Dalam kode ini, self mempertahankan blok, blok mempertahankan delegasi, dan tidak ada siklus (terlihat dari sini; delegasi dapat mempertahankan objek kami tapi itu di luar kendali kami saat ini). Kode ini tidak akan mengambil risiko kebocoran dengan cara yang sama, karena nilai properti delegasi ditangkap saat blok dibuat, alih-alih mendongak ketika dijalankan. Efek sampingnya adalah, jika Anda mengubah delegasi setelah blok ini dibuat, blok tersebut masih akan mengirim pesan pembaruan ke delegasi lama. Apakah itu mungkin terjadi atau tidak tergantung pada aplikasi Anda.
Meskipun Anda keren dengan perilaku itu, Anda masih tidak bisa menggunakan trik itu dalam kasus Anda:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Di sini Anda mengirimkan self
langsung ke delegasi dalam pemanggilan metode, jadi Anda harus mendapatkannya di suatu tempat. Jika Anda memiliki kendali atas definisi tipe blok, hal terbaik adalah meneruskan delegasi ke blok sebagai parameter:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Solusi ini menghindari siklus mempertahankan dan selalu memanggil delegasi saat ini.
Jika Anda tidak dapat mengubah blok, Anda bisa mengatasinya . Alasan mempertahankan siklus adalah peringatan, bukan kesalahan, adalah bahwa mereka tidak selalu mengeja malapetaka untuk aplikasi Anda. Jika MyDataProcessor
mampu melepaskan blok ketika operasi selesai, sebelum induknya akan mencoba melepaskannya, siklus akan rusak dan semuanya akan dibersihkan dengan benar. Jika Anda bisa yakin akan hal ini, maka hal yang benar untuk dilakukan adalah menggunakan a #pragma
untuk menekan peringatan untuk blok kode itu. (Atau gunakan flag kompiler per file. Tetapi jangan menonaktifkan peringatan untuk seluruh proyek.)
Anda juga bisa melihat menggunakan trik serupa di atas, menyatakan referensi lemah atau tidak dibatasi dan menggunakannya di blok. Sebagai contoh:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Ketiga hal di atas akan memberi Anda referensi tanpa mempertahankan hasilnya, meskipun mereka semua berperilaku sedikit berbeda: __weak
akan mencoba untuk nol referensi ketika objek dilepaskan; __unsafe_unretained
akan meninggalkan Anda dengan pointer yang tidak valid; __block
sebenarnya akan menambah tingkat tipuan lain dan memungkinkan Anda untuk mengubah nilai referensi dari dalam blok (tidak relevan dalam kasus ini, karena dp
tidak digunakan di tempat lain).
Apa yang terbaik akan tergantung pada kode apa yang dapat Anda ubah dan apa yang tidak bisa Anda ubah. Tapi semoga ini memberi Anda beberapa ide tentang bagaimana untuk melanjutkan.