Pertahankan siklus pada `self` dengan blok

167

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 constsalinan, menggunakan selfdalam blok dapat menghasilkan siklus tetap, haruskah blok itu disalin. Jadi, kita seharusnya menggunakan __blockuntuk memaksa blok untuk berurusan langsung dengan selfbukannya 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)?

Jonathan Sterling
sumber
3
Saya suka memanggil selfproksi saya thishanya untuk membalikkan keadaan. Dalam JavaScript saya sebut thispenutupan saya self, jadi rasanya enak dan seimbang. :)
devios1
Saya ingin tahu apakah ada tindakan setara yang perlu dilakukan jika saya menggunakan blok Swift
Ben Lu
@ Benu benar-benar! dalam penutupan Swift (dan fungsi-fungsi yang dapat diedarkan yang menyebutkan diri secara implisit atau eksplisit) akan mempertahankan diri. Kadang-kadang ini diinginkan, dan di lain waktu menciptakan siklus (karena penutupan itu sendiri dimiliki oleh diri sendiri (atau dimiliki oleh sesuatu yang dimiliki sendiri). Alasan utama ini terjadi adalah karena ARC.
Ethan
1
Untuk menghindari masalah, cara yang tepat untuk mendefinisikan 'diri' untuk digunakan dalam sebuah blok adalah '__typeof (diri) __lemah lemahSelf = diri;' untuk memiliki referensi yang lemah.
XLE_22

Jawaban:

169

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 __blockkelas 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 selfin 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, __blocktidak ada lagi istirahat mempertahankan siklus. Jika Anda mengkompilasi untuk ARC, Anda harus menggunakan __weakatau __unsafe_unretainedsebagai gantinya.

Lily Ballard
sumber
Tidak masalah! Jika ini menjawab pertanyaan untuk kepuasan Anda, saya akan sangat menghargai jika Anda dapat memilih ini sebagai jawaban yang benar untuk pertanyaan Anda. Jika tidak, beri tahu saya bagaimana saya bisa menjawab pertanyaan Anda dengan lebih baik.
Lily Ballard
4
Tidak masalah, Kevin. SO menunda Anda untuk segera memilih jawaban atas pertanyaan, jadi saya harus kembali lagi nanti. Bersulang.
Jonathan Sterling
__unsafe_unretained id bself = self;
caleb
4
@ JKLaiho: Tentu, __weakbaik-baik saja. Jika Anda tahu pasti bahwa objek tidak bisa keluar dari ruang lingkup ketika blok dipanggil maka __unsafe_unretainedakan sedikit lebih cepat, tetapi secara umum itu tidak akan membuat perbedaan. Jika Anda menggunakannya __weak, pastikan untuk melemparkannya ke __strongvariabel lokal dan mengujinya untuk yang non- nilsebelum melakukan sesuatu dengannya.
Lily Ballard
2
@Rpataata: Ya. __blockEfek samping dari tidak mempertahankan dan melepaskan itu murni karena ketidakmampuan untuk alasan yang tepat tentang itu. Dengan ARC, kompiler memperoleh kemampuan itu, dan __blocksekarang 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.
Lily Ballard
66

Cukup gunakan:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

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

__weak typeof(self)weakSelf = self;
3lvis
sumber
2
Dan apakah Anda kebetulan menemukannya :)?
Tieme
2
Anda dapat memeriksa videonya di sini - developer.apple.com/videos/wwdc/2011/…
nanjunda
Apakah Anda dapat referensi diri di dalam "someOtherMethod"? Akankah diri merujuk diri kita sendiri pada saat itu atau apakah itu juga akan menciptakan siklus penahan?
Oren
Hai @ Oren, jika Anda mencoba referensi diri di dalam "someOtherMethod" Anda akan mendapatkan peringatan Xcode. Pendekatan saya hanya membuat referensi diri yang lemah.
3lvis
1
Saya hanya mendapat peringatan ketika merujuk diri secara langsung di dalam blok. Menempatkan diri di dalam beberapa metode lain tidak menyebabkan peringatan. Apakah itu karena xcode tidak cukup pintar atau bukan masalah? Apakah merujuk diri sendiri di dalam beberapa metode lain sudah merujuk pada diri lemah sejak itu yang Anda panggil metode ini?
Oren
22

Ini mungkin jelas, tetapi Anda hanya perlu melakukan selfalias jelek ketika Anda tahu Anda akan mendapatkan siklus mempertahankan. Jika blok itu hanya satu tembakan maka saya pikir Anda dapat dengan aman mengabaikan retain on self. Kasus buruknya adalah ketika Anda memiliki blok sebagai antarmuka panggilan balik, misalnya. Seperti di sini:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

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:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

Dalam situasi ini saya tidak melakukan selfalias. 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 mungkin selfaliasing keluar sebagai praktik terbaik dalam jangka panjang.

zoul
sumber
6
Poin yang bagus. Ini hanya siklus mempertahankan jika diri menjaga blok hidup. Dalam hal blok yang tidak pernah disalin, atau blok dengan durasi terbatas yang dijamin (mis. Blok penyelesaian untuk animasi UIView), Anda tidak perlu khawatir tentang hal itu.
Lily Ballard
6
Pada prinsipnya, Anda benar. Namun, jika Anda menjalankan kode dalam contoh, Anda akan crash. Properti blok harus selalu dinyatakan sebagai copy, bukan retain. Jika mereka hanya retain, 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)
Dave DeLong
Ah, tentu saja, salah ketik. Saya melewati retainfase beberapa waktu yang lalu dan dengan cepat menyadari hal yang Anda katakan :) Terima kasih!
zoul
Saya yakin retainbenar-benar diabaikan untuk blok (kecuali mereka sudah pindah dari tumpukan dengan copy).
Steven Fisher
@Dave DeLong, Tidak, itu tidak akan crash karena @property (retain) digunakan untuk referensi objek saja, bukan blok .. Tidak perlu salinan untuk digunakan di sini sama sekali ..
DeniziOS
20

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:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

Anda tidak perlu blockSelf dalam kasus kedua karena objek panggilan tidak memiliki blok di dalamnya yang akan menyebabkan siklus tetap ketika Anda referensi sendiri:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 
possen
sumber
Itu kesalahpahaman umum dan bisa berbahaya, karena blok yang harus dipertahankan selfmungkin 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.
Carl Veazey
9

Ingat juga bahwa siklus penahan dapat terjadi jika blok Anda merujuk ke objek lain yang kemudian dipertahankanself .

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.

  • Gunakan hanya blok untuk penyelesaian , dan bukan untuk acara terbuka. Misalnya, gunakan blok untuk metode suka 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.
  • Lakukan tarian referensi lemah yang Anda gambarkan.
  • Berikan metode untuk membersihkan objek Anda sebelum dirilis, yang "memutus" objek dari objek server yang mungkin menyimpan referensi untuk itu; dan panggil metode ini sebelum memanggil rilis pada objek. Meskipun metode ini baik-baik saja jika objek Anda hanya memiliki satu klien (atau tunggal dalam beberapa konteks), tetapi akan rusak jika memiliki beberapa klien. Anda pada dasarnya mengalahkan mekanisme penghitungan retain di sini; ini mirip dengan menelepon, deallocbukan release.

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 iklankan setEventDelegate: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:

- (void) dealloc {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}

Jika Anda mencoba melepaskan kaitan blok yang mungkin merujuk selfdari dalam dealloc, Anda sudah dalam masalah. deallocmungkin tidak pernah dipanggil karena mempertahankan siklus yang disebabkan oleh referensi di blok, yang berarti bahwa objek Anda hanya akan bocor sampai objek server dideallocated.

Dave R
sumber
GC memang membantu jika Anda menggunakannya dengan __weaktepat.
tc.
Melacak pengumpulan sampah tentu saja dapat menangani siklus penahan. Pertahankan siklus hanya merupakan masalah untuk lingkungan penghitungan referensi
newacct
Hanya agar semua orang tahu, pengumpulan sampah tidak digunakan lagi di OS X v10.8 untuk Penghitungan Referensi Otomatis (ARC) dan dijadwalkan untuk dihapus di versi OS X yang akan datang ( developer.apple.com/library/mac/#releasenotes / ObjectiveC / ... ).
Ricardo Sanchez-Saez
1

__block __unsafe_unretainedpengubah 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.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];
b1gbr0
sumber
Tidakkah lebih aman menggunakan __weak daripada __block untuk menghindari kebutuhan nilling variabel setelah menggunakannya? Maksud saya, solusi ini sangat bagus jika Anda ingin memutus siklus jenis lain, tetapi tentu saja saya tidak melihat kelebihan khusus untuk siklus "diri" yang dipertahankannya.
ale0xB
Anda tidak dapat menggunakan __weak jika target platform Anda adalah iOS 4.x. Terkadang Anda juga membutuhkan kode di blok telah dieksekusi untuk objek yang valid, bukan untuk nihil.
b1gbr0
1

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:

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

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

Yuri Solodkin
sumber
1
Sebelum menunjukkan kode yang disederhanakan akan lebih baik untuk mengetahui apa yang ada di baliknya, semua orang harus tahu dua baris kode yang sebenarnya.
Alex Cio
0

Bagaimana dengan ini?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

Saya tidak mendapatkan peringatan kompiler lagi.

Καrτhικ
sumber
-1

Blok: siklus penahan akan terjadi karena mengandung blok yang direferensikan di blok; Jika Anda membuat salinan blok dan menggunakan variabel anggota, diri akan tetap.

yijiankaka
sumber