Saya khawatir pertanyaan ini cukup mendasar, tetapi saya pikir ini relevan dengan banyak programmer Objective-C yang masuk ke blok.
Apa yang saya dengar adalah bahwa karena blok menangkap variabel lokal yang dirujuk di dalamnya sebagai const
salinan, menggunakan self
dalam blok dapat menghasilkan siklus tetap, haruskah blok itu disalin. Jadi, kita seharusnya menggunakan __block
untuk memaksa blok untuk berurusan langsung dengan self
bukannya disalin.
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
bukannya adil
[someObject messageWithBlock:^{ [self doSomething]; }];
Yang ingin saya ketahui adalah sebagai berikut: jika ini benar, adakah cara agar saya dapat menghindari keburukan (selain menggunakan GC)?
objective-c
memory-management
objective-c-blocks
Jonathan Sterling
sumber
sumber
self
proksi sayathis
hanya untuk membalikkan keadaan. Dalam JavaScript saya sebutthis
penutupan sayaself
, jadi rasanya enak dan seimbang. :)Jawaban:
Sebenarnya, fakta bahwa itu adalah salinan const tidak ada hubungannya dengan masalah ini. Blok akan mempertahankan nilai objek apa pun yang ditangkap saat dibuat. Kebetulan bahwa solusi untuk masalah const-copy identik dengan solusi untuk masalah retain; yaitu, menggunakan
__block
kelas penyimpanan untuk variabel.Bagaimanapun, untuk menjawab pertanyaan Anda, tidak ada alternatif nyata di sini. Jika Anda merancang API berbasis blok Anda sendiri, dan masuk akal untuk melakukannya, Anda dapat meminta agar blok tersebut melewati nilai
self
in sebagai argumen. Sayangnya, ini tidak masuk akal untuk sebagian besar API.Harap dicatat bahwa mereferensi ivar memiliki masalah yang sama persis. Jika Anda perlu referensi ivar di blok Anda, gunakan properti atau gantinya gunakan
bself->ivar
.Tambahan: Ketika dikompilasi sebagai ARC,
__block
tidak ada lagi istirahat mempertahankan siklus. Jika Anda mengkompilasi untuk ARC, Anda harus menggunakan__weak
atau__unsafe_unretained
sebagai gantinya.sumber
__weak
baik-baik saja. Jika Anda tahu pasti bahwa objek tidak bisa keluar dari ruang lingkup ketika blok dipanggil maka__unsafe_unretained
akan sedikit lebih cepat, tetapi secara umum itu tidak akan membuat perbedaan. Jika Anda menggunakannya__weak
, pastikan untuk melemparkannya ke__strong
variabel lokal dan mengujinya untuk yang non-nil
sebelum melakukan sesuatu dengannya.__block
Efek samping dari tidak mempertahankan dan melepaskan itu murni karena ketidakmampuan untuk alasan yang tepat tentang itu. Dengan ARC, kompiler memperoleh kemampuan itu, dan__block
sekarang mempertahankan dan melepaskan. Jika Anda perlu menghindari itu, Anda harus menggunakan__unsafe_unretained
, yang menginstruksikan kompiler untuk tidak melakukan retain atau rilis pada nilai dalam variabel.Cukup gunakan:
Untuk informasi lebih lanjut: WWDC 2011 - Blok dan Grand Central Dispatch in Practice .
https://developer.apple.com/videos/wwdc/2011/?id=308
Catatan: jika itu tidak berhasil, Anda dapat mencoba
sumber
Ini mungkin jelas, tetapi Anda hanya perlu melakukan
self
alias jelek ketika Anda tahu Anda akan mendapatkan siklus mempertahankan. Jika blok itu hanya satu tembakan maka saya pikir Anda dapat dengan aman mengabaikan retain onself
. Kasus buruknya adalah ketika Anda memiliki blok sebagai antarmuka panggilan balik, misalnya. Seperti di sini:Di sini API tidak masuk akal, tetapi akan masuk akal ketika berkomunikasi dengan superclass, misalnya. Kami mempertahankan handler buffer, handler buffer mempertahankan kami. Bandingkan dengan yang seperti ini:
Dalam situasi ini saya tidak melakukan
self
alias. Anda memang mendapatkan siklus tetap, tetapi operasi ini berumur pendek dan blok akan keluar dari memori pada akhirnya, memutus siklus. Tapi pengalaman saya dengan balok sangat kecil dan mungkinself
aliasing keluar sebagai praktik terbaik dalam jangka panjang.sumber
copy
, bukanretain
. Jika mereka hanyaretain
, maka tidak ada jaminan mereka akan dipindahkan dari tumpukan, yang berarti ketika Anda pergi untuk menjalankannya, itu tidak akan ada lagi. (dan penyalinan dan blok yang sudah disalin dioptimalkan ke retain)retain
fase beberapa waktu yang lalu dan dengan cepat menyadari hal yang Anda katakan :) Terima kasih!retain
benar-benar diabaikan untuk blok (kecuali mereka sudah pindah dari tumpukan dengancopy
).Posting jawaban lain karena ini juga masalah buat saya. Saya awalnya berpikir saya harus menggunakan blockSelf di mana saja ada referensi diri di dalam blok. Ini bukan kasusnya, itu hanya ketika objek itu sendiri memiliki blok di dalamnya. Dan pada kenyataannya, jika Anda menggunakan blockSelf dalam kasus-kasus ini objek dapat dibatalkan sebelum Anda mendapatkan kembali dari blok dan kemudian akan crash ketika mencoba memanggilnya, jadi jelas Anda ingin diri dipertahankan sampai respons datang kembali.
Kasus pertama menunjukkan kapan siklus penahan akan terjadi karena berisi blok yang direferensikan di blok:
Anda tidak perlu blockSelf dalam kasus kedua karena objek panggilan tidak memiliki blok di dalamnya yang akan menyebabkan siklus tetap ketika Anda referensi sendiri:
sumber
self
mungkin bukan karena orang yang terlalu menerapkan perbaikan ini. Ini adalah contoh yang baik untuk menghindari mempertahankan siklus dalam kode non-ARC, terima kasih telah memposting.Ingat juga bahwa siklus penahan dapat terjadi jika blok Anda merujuk ke objek lain yang kemudian dipertahankan
self
.Saya tidak yakin bahwa Pengumpulan Sampah dapat membantu dalam mempertahankan siklus ini. Jika objek mempertahankan blok (yang saya sebut objek server) hidup lebih lama
self
(objek klien), referensi keself
dalam blok tidak akan dianggap siklik sampai objek penahan itu sendiri dilepaskan. Jika objek server jauh lebih lama dari kliennya, Anda mungkin memiliki kebocoran memori yang signifikan.Karena tidak ada solusi bersih, saya akan merekomendasikan solusi berikut. Jangan ragu untuk memilih satu atau lebih dari mereka untuk memperbaiki masalah Anda.
doSomethingAndWhenDoneExecuteThisBlock:
, dan bukan metode sukasetNotificationHandlerBlock:
. Blok yang digunakan untuk penyelesaian memiliki akhir yang pasti, dan harus dirilis oleh objek server setelah dievaluasi. Ini mencegah siklus mempertahankan hidup terlalu lama bahkan jika itu terjadi.dealloc
bukanrelease
.Jika Anda menulis objek server, ambil argumen blokir hanya untuk penyelesaian. Jangan terima argumen blokir untuk panggilan balik, seperti
setEventHandlerBlock:
. Alih-alih, kembalilah ke pola delegasi klasik: buat protokol formal, dan iklankansetEventDelegate:
metode. Jangan mempertahankan delegasi. Jika Anda bahkan tidak ingin membuat protokol formal, terima pemilih sebagai panggilan balik delegasi.Dan terakhir, pola ini seharusnya membunyikan alarm:
Jika Anda mencoba melepaskan kaitan blok yang mungkin merujuk
self
dari dalamdealloc
, Anda sudah dalam masalah.dealloc
mungkin tidak pernah dipanggil karena mempertahankan siklus yang disebabkan oleh referensi di blok, yang berarti bahwa objek Anda hanya akan bocor sampai objek server dideallocated.sumber
__weak
tepat.__block __unsafe_unretained
pengubah yang disarankan dalam pos Kevin dapat menyebabkan pengecualian akses buruk jika blok dieksekusi di utas yang berbeda. Lebih baik gunakan pengubah __block hanya untuk variabel temp dan membuatnya nihil setelah penggunaan.sumber
Anda dapat menggunakan libextobjc library. Ini cukup populer, digunakan di ReactiveCocoa misalnya. https://github.com/jspahrsummers/libextobjc
Ini menyediakan 2 macro @weakify dan @strongify, sehingga Anda dapat memiliki:
Ini mencegah referensi kuat langsung sehingga kami tidak masuk ke siklus mempertahankan diri. Dan juga, itu mencegah diri dari menjadi nihil di tengah jalan, tetapi masih benar mengurangi jumlah penyimpanan. Lebih banyak di tautan ini: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
sumber
Bagaimana dengan ini?
Saya tidak mendapatkan peringatan kompiler lagi.
sumber
Blok: siklus penahan akan terjadi karena mengandung blok yang direferensikan di blok; Jika Anda membuat salinan blok dan menggunakan variabel anggota, diri akan tetap.
sumber