it-swarm-id.com

Bagaimana cara saya menghindari menangkap diri dalam blok ketika menerapkan API?

Saya memiliki aplikasi yang berfungsi dan saya sedang berusaha mengubahnya menjadi ARC di Xcode 4.2. Salah satu peringatan pra-cek melibatkan penangkapan self dengan kuat di blok yang mengarah ke siklus penyimpanan. Saya telah membuat contoh kode sederhana untuk menggambarkan masalah ini. Saya percaya saya mengerti apa artinya ini tetapi saya tidak yakin cara yang "benar" atau direkomendasikan untuk mengimplementasikan skenario jenis ini.

  • diri adalah turunan dari kelas MyAPI
  • kode di bawah ini disederhanakan untuk hanya menampilkan interaksi dengan objek dan blok yang relevan dengan pertanyaan saya
  • berasumsi bahwa MyAPI mendapatkan data dari sumber jarak jauh dan MyDataProcessor bekerja pada data itu dan menghasilkan output
  • prosesor dikonfigurasikan dengan blok untuk mengkomunikasikan kemajuan & status

contoh kode:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

Pertanyaan: apa yang saya lakukan "salah" dan/atau bagaimana ini harus dimodifikasi agar sesuai dengan konvensi ARC?

222
XJones

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

Variabel kata kunci __block menandai yang dapat dimodifikasi di dalam blok (kami tidak melakukan itu) tetapi juga mereka tidak secara otomatis dipertahankan ketika blok tersebut dipertahankan (kecuali jika 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 dari __block perubahan dan referensi akan dipertahankan, dalam hal ini Anda harus menyatakannya __weak sebagai gantinya.

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 delegasinya dan mengirim metode delegasi. Jika semua hal lain di aplikasi Anda melepaskan referensi ke objek ini, jumlah tetapnya tidak akan nol (karena blok mengarah ke sana) dan blok tidak melakukan kesalahan (karena objek menunjuk ke sana) dan sebagainya sepasang objek akan bocor ke tumpukan, menempati memori tetapi selamanya tidak dapat dijangkau tanpa debugger. Tragis, sungguh.

Kasing itu dapat 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 tangan kami sekarang). 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 yang lama. Apakah itu mungkin terjadi atau tidak tergantung pada aplikasi Anda.

Meskipun Anda keren dengan perilaku itu, Anda tetap 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 sana 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 dapat menanganinya . Alasan mempertahankan siklus adalah peringatan, bukan kesalahan, adalah bahwa mereka tidak selalu mengeja Doom untuk aplikasi Anda. Jika MyDataProcessor dapat 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 #pragma untuk menekan peringatan untuk blok kode itu. (Atau gunakan flag kompiler per file. Tetapi jangan menonaktifkan peringatan untuk seluruh proyek.)

Anda juga dapat 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 akan benar-benar menambahkan tingkat tipuan yang lain dan memungkinkan Anda untuk mengubah nilai referensi dari dalam blok (tidak relevan dalam kasus ini, karena dp tidak digunakan di tempat lain).

Apa terbaik akan tergantung pada kode apa yang dapat Anda ubah dan apa yang tidak dapat Anda ubah. Tapi semoga ini memberi Anda beberapa ide tentang bagaimana untuk melanjutkan.

509
benzado

Ada juga opsi untuk menekan peringatan ketika Anda yakin bahwa siklus akan rusak di masa depan:

#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

Dengan begitu Anda tidak perlu berkutat dengan __weak, self aliasing dan awalan ivar eksplisit.

25
zoul

Untuk solusi umum, saya memiliki ini mendefinisikan di header precompile. Menghindari pengambilan dan masih memungkinkan bantuan kompiler dengan menghindari penggunaan id

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

Kemudian dalam kode dapat Anda lakukan:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};
14
Damien Pontifex

Saya percaya solusi tanpa ARC juga bekerja dengan ARC, menggunakan kata kunci __block:

EDIT: Per Transisi ke ARC Release Notes , objek yang dideklarasikan dengan __block penyimpanan masih dipertahankan. Gunakan __weak (lebih disukai) atau __unsafe_unretained (untuk kompatibilitas mundur).

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];
11
Tony

Menggabungkan beberapa jawaban lain, inilah yang saya gunakan sekarang untuk diri yang diketik lemah untuk digunakan dalam blok:

__typeof(self) __weak welf = self;

Saya menetapkan itu sebagai XCode Code Snippet dengan awalan penyelesaian "welf" dalam metode/fungsi, yang mengenai setelah mengetik hanya "kami".

peringatan => "menangkap diri di dalam blok cenderung memimpin siklus tetap"

ketika Anda merujuk diri atau propertinya di dalam blok yang sangat dipertahankan sendiri daripada yang ditunjukkan peringatan di atas.

jadi untuk menghindarinya kita harus membuatnya seminggu ref

__weak typeof(self) weakSelf = self;

jadi alih-alih menggunakan

blockname=^{
    self.PROPERTY =something;
}

kita harus gunakan

blockname=^{
    weakSelf.PROPERTY =something;
}

catatan: mempertahankan siklus biasanya terjadi ketika beberapa bagaimana dua objek merujuk satu sama lain dimana keduanya memiliki jumlah referensi = 1 dan metode delloc mereka tidak pernah dipanggil.

6
Anurag Bhakuni

Cara baru untuk melakukan ini adalah dengan menggunakan @weakify dan @strongify marco

@weakify(self);
[self methodThatTakesABlock:^ {
    @strongify(self);
    [self doSomething];
}];

Info Lebih Lanjut tentang @Weakify @Strongify Marco

1
Jun Jie Gan