Selalu berikan referensi diri yang lemah ke dalam blok di ARC?

252

Saya sedikit bingung tentang penggunaan blok di Objective-C. Saat ini saya menggunakan ARC dan saya memiliki cukup banyak blok di aplikasi saya, saat ini selalu mengacu pada selfreferensi yang lemah. selfMungkinkah itu penyebab blok-blok ini mempertahankan dan menjaganya agar tidak dapat dialokasikan kembali? Pertanyaannya adalah, haruskah saya selalu menggunakan weakreferensi selfdi dalam blok?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}
the_critic
sumber
Jika Anda menginginkan wacana mendalam tentang topik ini, baca dhoerl.wordpress.com/2013/04/23/…
David H

Jawaban:

721

Ini membantu untuk tidak fokus pada strongatau weakbagian dari diskusi. Alih-alih fokus pada bagian siklus .

Siklus penahanan adalah lingkaran yang terjadi ketika Objek A mempertahankan Objek B, dan Obyek B mempertahankan Objek A. Dalam situasi itu, jika salah satu objek dilepaskan:

  • Objek A tidak akan dibatalkan alokasinya karena Obyek B memiliki referensi untuk itu.
  • Tetapi Objek B tidak akan pernah dapat dibatalkan alokasinya selama Obyek A memiliki referensi untuk itu.
  • Tetapi Objek A tidak akan pernah dapat dibatalkan alokasinya karena Obyek B memiliki referensi untuk itu.
  • ad infinitum

Dengan demikian, kedua objek hanya akan berkeliaran di memori untuk kehidupan program meskipun mereka harus, jika semuanya bekerja dengan baik, tidak dapat dialokasikan kembali.

Jadi, yang kami khawatirkan adalah mempertahankan siklus , dan tidak ada apa-apa tentang blok di dalam dan dari diri mereka yang menciptakan siklus ini. Ini bukan masalah, misalnya:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

Blok tetap self, tetapi selftidak mempertahankan blok. Jika satu atau yang lainnya dilepaskan, tidak ada siklus yang dibuat dan semuanya akan dibatalkan alokasi sebagaimana mestinya.

Di mana Anda mendapat masalah adalah sesuatu seperti:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Sekarang, objek Anda ( self) memiliki strongreferensi eksplisit ke blok. Dan blok memiliki referensi kuat yang tersiratself . Itu sebuah siklus, dan sekarang tidak ada objek yang akan dialokasikan dengan benar.

Karena, dalam situasi seperti ini, self menurut definisi sudah memiliki strongreferensi ke blok, biasanya paling mudah untuk diselesaikan dengan membuat referensi yang secara eksplisit lemah selfuntuk blok yang akan digunakan:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

Tapi ini seharusnya bukan pola default yang Anda ikuti ketika berhadapan dengan blok panggilan itu self! Ini seharusnya hanya digunakan untuk memutus siklus retensi antara diri dan blok. Jika Anda mengadopsi pola ini di mana-mana, Anda akan berisiko melewatkan blok ke sesuatu yang dieksekusi setelah selfdibatalkan alokasi.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];
jemmons
sumber
2
Saya tidak yakin A mempertahankan B, B mempertahankan A akan melakukan siklus yang tak terbatas. Dari perspektif jumlah referensi, jumlah referensi A dan B adalah 1. Apa yang menyebabkan siklus penahan untuk situasi ini adalah ketika tidak ada grup lain yang memiliki referensi kuat tentang A dan B di luar - itu berarti kita tidak dapat mencapai dua objek ini (kita tidak dapat mengontrol A untuk melepaskan B dan sebaliknya), karenanya, A dan B saling referensi untuk menjaga agar keduanya tetap hidup.
Danyun Liu
@ Danyun Meskipun benar bahwa mempertahankan siklus antara A dan B tidak dapat dipulihkan sampai semua referensi lain untuk objek-objek ini telah dirilis, itu tidak membuatnya menjadi kurang dari siklus. Sebaliknya, hanya karena siklus tertentu dapat dipulihkan tidak berarti tidak masalah untuk memilikinya dalam kode Anda. Mempertahankan siklus adalah bau desain yang buruk.
jemmons
@ Jonemons Ya, kita harus selalu menghindari desain siklus mempertahankan sebanyak yang kita bisa.
Danyun Liu
1
@ Guru Tidak mungkin bagi saya untuk mengatakannya. Tergantung sepenuhnya pada implementasi -setCompleteionBlockWithSuccess:failure:metode Anda . Tetapi jika paginatordimiliki oleh ViewController, dan blok-blok ini tidak dipanggil setelah ViewControllerakan dirilis, menggunakan __weakreferensi akan menjadi langkah aman (karena selfmemiliki hal yang memiliki blok, dan kemungkinan masih ada ketika blok menyebutnya meskipun mereka tidak mempertahankannya). Tapi itu banyak "jika". Ini benar-benar tergantung pada apa yang seharusnya dilakukan.
jemmons
1
@ Tan Tidak, dan ini adalah jantung dari masalah manajemen memori dengan blok / penutupan. Objek bisa dialokasikan ketika tidak ada yang memilikinya. MyObjectdan SomeOtherObjectkeduanya memiliki blok. Tetapi karena kembali referensi blok untuk MyObjectadalah weak, blok tidak sendiri MyObject. Jadi sementara blok dijamin ada selama salah satu MyObject atau SomeOtherObject ada, tidak ada jaminan yang MyObjectakan ada selama blok itu ada. MyObjectdapat sepenuhnya dialokasikan dan, selama SomeOtherObjectmasih ada, blok akan tetap ada.
jemmons
26

Anda tidak harus selalu menggunakan referensi yang lemah. Jika blok Anda tidak dipertahankan, tetapi dieksekusi dan kemudian dibuang, Anda dapat menangkap diri dengan kuat, karena itu tidak akan membuat siklus mempertahankan. Dalam beberapa kasus, Anda bahkan ingin agar blok menahan diri sampai selesainya blok sehingga tidak membatalkan alokasi sebelum waktunya. Namun, jika Anda menangkap blok dengan kuat, dan di dalam diri menangkap, itu akan membuat siklus tetap.

Leo Natan
sumber
Yah saya hanya menjalankan blok sebagai panggilan balik dan saya tidak ingin diri sendiri sama sekali. Tapi sepertinya saya membuat mempertahankan siklus karena View Controller yang dipermasalahkan tidak dapat dibatalkan alokasi ...
the_critic
1
Misalnya, jika Anda memiliki item tombol bilah yang dipertahankan oleh pengontrol tampilan, dan Anda menangkap diri dengan kuat di blok itu, akan ada siklus penahan.
Leo Natan
1
@MartinE. Anda hanya perlu memperbarui pertanyaan Anda dengan contoh kode. Leo benar (+1), itu tidak selalu menyebabkan siklus referensi yang kuat, tetapi mungkin, tergantung pada bagaimana Anda menggunakan blok ini. Akan lebih mudah bagi kami untuk membantu Anda jika Anda memberikan cuplikan kode.
Rob
@LeoNatan Saya mengerti gagasan mempertahankan siklus, tapi saya tidak begitu yakin apa yang terjadi di blok, sehingga membingungkan saya sedikit
the_critic
Dalam kode di atas, instance diri Anda akan dirilis setelah operasi selesai dan blok dilepaskan. Anda harus membaca tentang bagaimana blok bekerja dan apa dan kapan mereka menangkapnya.
Leo Natan
26

Saya sangat setuju dengan @jemmons:

Tapi ini seharusnya bukan pola default yang Anda ikuti ketika berhadapan dengan blok yang menyebut diri! Ini seharusnya hanya digunakan untuk memutus siklus retensi antara diri dan blok. Jika Anda mengadopsi pola ini di mana-mana, Anda akan berisiko menularkan blok ke sesuatu yang dieksekusi setelah self deallocated.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Untuk mengatasi masalah ini orang dapat mendefinisikan referensi yang kuat di bagian weakSelfdalam blok:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Ilker Baltaci
sumber
3
Tidakkah strongSelf menambah hitungan referensi untuk lemahSelf? Jadi menciptakan siklus penahan?
mskw
12
Pertahankan siklus hanya masalah jika mereka ada dalam keadaan statis objek. Sementara kode dieksekusi dan kondisinya dalam fluks, beberapa dan mungkin mempertahankan redundan baik-baik saja. Ngomong-ngomong mengenai pola ini, menangkap referensi yang kuat di sana tidak melakukan apa-apa untuk kasus deallocated diri sebelum blok berjalan, itu masih bisa terjadi. Itu memastikan diri tidak mendapatkan deallocated saat menjalankan blok. Ini penting jika blok melakukan operasi async itu sendiri memberikan jendela untuk itu terjadi.
Pierre Houston
@smallduck, penjelasan Anda sangat bagus. Sekarang saya mengerti ini lebih baik. Buku-buku tidak membahas hal ini, terima kasih.
Huibin Zhang
5
Ini bukan contoh yang baik dari strongSelf, karena penambahan eksplisit dari strongSelf persis seperti apa yang akan dilakukan runtime: pada baris doSomething, referensi yang kuat diambil selama durasi pemanggilan metode. Jika lemahSelf sudah tidak valid, ref kuat adalah nihil dan pemanggilan metode adalah no-op. Di mana strongSelf membantu adalah jika Anda memiliki serangkaian operasi, atau mengakses bidang anggota ( ->), di mana Anda ingin memastikan Anda benar-benar mendapatkan referensi yang valid dan menahannya terus menerus di seluruh rangkaian operasi, misalnyaif ( strongSelf ) { /* several operations */ }
Ethan
19

Seperti yang ditunjukkan Leo, kode yang Anda tambahkan ke pertanyaan Anda tidak akan menyarankan siklus referensi yang kuat (alias, pertahankan siklus). Salah satu masalah terkait operasi yang dapat menyebabkan siklus referensi yang kuat adalah jika operasi tidak dirilis. Walaupun cuplikan kode Anda menunjukkan bahwa Anda belum mendefinisikan operasi Anda bersamaan, tetapi jika Anda melakukannya, itu tidak akan dirilis jika Anda tidak pernah diposting isFinished, atau jika Anda memiliki dependensi melingkar, atau sesuatu seperti itu. Dan jika operasi tidak dirilis, pengontrol tampilan tidak akan dirilis juga. Saya akan menyarankan menambahkan breakpoint atau NSLogdalam deallocmetode operasi Anda dan mengkonfirmasi bahwa dipanggil.

Kamu berkata:

Saya memahami gagasan mempertahankan siklus, tetapi saya tidak yakin apa yang terjadi dalam blok, sehingga membingungkan saya sedikit

Masalah siklus mempertahankan (siklus referensi kuat) yang terjadi dengan blok sama seperti masalah siklus mempertahankan yang Anda kenal. Blok akan mempertahankan referensi kuat ke objek apa pun yang muncul di dalam blok, dan itu tidak akan merilis referensi kuat sampai blok itu sendiri dilepaskan. Jadi, jika referensi blok self, atau bahkan hanya referensi variabel instan self, yang akan mempertahankan referensi kuat untuk diri sendiri, itu tidak diselesaikan sampai blok dilepaskan (atau dalam kasus ini, sampai NSOperationsubkelas dilepaskan.

Untuk informasi lebih lanjut, lihat bagian Hindari Siklus Referensi Kuat ketika Mengambil bagian mandiri Pemrograman dengan Objective-C: Bekerja dengan dokumen Blok .

Jika view controller Anda masih belum dirilis, Anda hanya perlu mengidentifikasi di mana referensi kuat yang belum terselesaikan berada (dengan asumsi Anda mengonfirmasi bahwa NSOperationdeallocated). Contoh umum adalah penggunaan pengulangan NSTimer. Atau beberapa kebiasaan delegateatau objek lain yang secara keliru mempertahankan strongreferensi. Anda sering dapat menggunakan Instrumen untuk melacak di mana objek mendapatkan referensi kuat mereka, misalnya:

catat jumlah referensi dalam Xcode 6

Atau di Xcode 5:

catat jumlah referensi dalam Xcode 5

rampok
sumber
1
Contoh lain adalah jika operasi dipertahankan dalam pencipta blok dan tidak dirilis setelah selesai. Beri +1 pada tulisan yang bagus!
Leo Natan
@LeoNatan Setuju, meskipun potongan kode menyatakannya sebagai variabel lokal yang akan dirilis jika dia menggunakan ARC. Tapi kamu benar sekali!
Rob
1
Ya, saya hanya memberi contoh, seperti yang diminta OP pada jawaban yang lain.
Leo Natan
Omong-omong, Xcode 8 memiliki "Debug Memory Graph", yang bahkan merupakan cara yang lebih mudah untuk menemukan referensi kuat ke objek yang belum dirilis. Lihat stackoverflow.com/questions/30992338/… .
Rob
0

Beberapa penjelasan mengabaikan kondisi tentang siklus penahan [Jika sekelompok objek dihubungkan oleh lingkaran hubungan yang kuat, mereka menjaga satu sama lain hidup meskipun tidak ada referensi kuat dari luar grup.] Untuk informasi lebih lanjut, baca dokumen

Danyun Liu
sumber
-2

Ini adalah bagaimana Anda dapat menggunakan diri di dalam blok:

// memanggil blok

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ranjeet Singh
sumber