menangkap diri dengan kuat di blok ini cenderung mengarah pada siklus penahan

207

Bagaimana saya bisa menghindari peringatan ini di xcode. Berikut ini cuplikan kode:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];
pengguna1845209
sumber
Apakah timerDispada properti di kelas?
Tim
Ya, @ properti (nonatomik, kuat) UILabel * timerDisp;
user1845209
2
Apa ini: player(AVPlayer object)dan timerDisp(UILabel)?
Carl Veazey
Pemain AVPlayer *; UILabel * timerDisp;
user1845209
5
Pertanyaan sebenarnya adalah bagaimana cara membungkam peringatan ini tanpa referensi lemah yang tidak perlu pada diri sendiri, ketika Anda tahu referensi melingkar akan rusak (mis. Jika Anda selalu menghapus referensi ketika permintaan jaringan selesai).
Glenn Maynard

Jawaban:

514

Pengambilan dari selfsini datang dengan akses properti implisit self.timerDispAnda - Anda tidak dapat merujuk selfatau properti selfdari dalam blok yang akan sangat dipertahankan oleh self.

Anda dapat menyiasatinya dengan membuat referensi yang lemah selfsebelum mengakses timerDispdi dalam blok Anda:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];
Tim
sumber
13
Coba gunakan __unsafe_unretainedsebagai gantinya.
Tim
63
Terselesaikan. gunakan ini sebagai gantinya: __unsafe_unretained typeof (self) weakSelf = self; terima kasih atas bantuan
@Tim
1
Jawaban yang bagus, tetapi saya mengambil masalah kecil dengan Anda mengatakan: "Anda tidak bisa merujuk pada diri sendiri atau properti dari dalam blok yang akan sangat dipertahankan oleh diri sendiri." Ini tidak sepenuhnya benar. Silakan lihat jawaban saya di bawah ini. Lebih baik mengatakan, "Anda harus sangat berhati-hati jika merujuk pada diri sendiri ..."
Chris Suter
8
Saya tidak melihat siklus tetap dalam kode OP. Blok tidak sangat dipertahankan oleh self, itu dipertahankan oleh antrian pengiriman utama. Apakah aku salah?
erikprice
3
@erikprice: Anda tidak salah. Saya menafsirkan pertanyaan terutama tentang kesalahan yang disajikan Xcode ("Bagaimana saya bisa menghindari peringatan ini dalam xcode"), daripada tentang keberadaan sebenarnya dari siklus penyimpanan. Anda benar dalam mengatakan tidak ada siklus mempertahankan yang terbukti hanya dari potongan OP yang disediakan.
Tim
52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

Dan satu hal yang sangat penting untuk diingat: jangan gunakan variabel instan langsung di blok, gunakan itu sebagai properti dari objek yang lemah, contoh:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

dan jangan lupa lakukan:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

masalah lain dapat muncul jika Anda akan melewati salinan lemah yang tidak disimpan oleh objek siapa pun:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

jika vcToGoakan dibatalkan alokasi dan kemudian blok ini dipecat Saya yakin Anda akan mendapatkan crash dengan pemilih yang tidak dikenal ke tempat sampah yang berisi vcToGo_variabel sekarang. Cobalah untuk mengendalikannya.

iiFreeman
sumber
3
Ini akan menjadi jawaban yang lebih kuat jika Anda juga menjelaskannya.
Eric J.
43

Versi yang lebih baik

__strong typeof(self) strongSelf = weakSelf;

Buat referensi yang kuat untuk versi lemah itu sebagai baris pertama di blok Anda. Jika diri masih ada ketika blok mulai mengeksekusi dan belum jatuh kembali ke nol, baris ini memastikan itu tetap ada sepanjang umur eksekusi blok.

Jadi semuanya akan seperti ini:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Saya telah membaca artikel ini berkali-kali. Ini adalah artikel yang sangat bagus oleh Erica Sadun tentang Cara Menghindari Masalah Saat Menggunakan Blok Dan NSNotificationCenter


Pembaruan cepat:

Misalnya, dengan cepat metode sederhana dengan blok sukses adalah:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Ketika kita memanggil metode ini dan perlu digunakan selfdi blok sukses. Kami akan menggunakan fitur [weak self]dan guard let.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Tarian kuat-lemah ini digunakan oleh proyek open source populer Alamofire.

Untuk info lebih lanjut, lihat panduan gaya cepat

Warif Akhand Rishi
sumber
Bagaimana jika Anda melakukannya di typeof(self) strongSelf = self;luar blok (bukan __weak) kemudian di blok tersebut dikatakan strongSelf = nil;setelah digunakan? Saya tidak melihat bagaimana contoh Anda memastikan bahwa kelemahan tidak nol pada saat blok dijalankan.
Matt
Untuk menghindari kemungkinan mempertahankan siklus, kami membuat referensi mandiri yang lemah di luar blok apa pun yang menggunakan diri dalam kodenya. Dalam cara Anda, Anda harus memastikan bahwa blok dijalankan. Blok kode lain Anda sekarang bertanggung jawab untuk membebaskan memori yang sebelumnya Anda simpan.
Warif Akhand Rishi
@Matt tujuan dari contoh ini bukan untuk membuat yang lemah tetap dipertahankan. Tujuannya adalah, jika yang lemah tidak nol, buat referensi yang kuat di dalam blok. Jadi begitu blok mulai dijalankan dengan diri, diri tidak menjadi nol di dalam blok.
Warif Akhand Rishi
15

Dalam jawaban lain, Tim berkata:

Anda tidak dapat merujuk diri atau properti pada diri dari dalam blok yang akan sangat dipertahankan oleh diri.

Ini tidak sepenuhnya benar. Tidak apa-apa bagi Anda untuk melakukan ini selama Anda memutus siklus di beberapa titik. Sebagai contoh, katakanlah Anda memiliki timer yang menyala yang memiliki blok yang mempertahankan diri dan Anda juga menyimpan referensi yang kuat untuk timer itu sendiri. Ini sangat baik jika Anda selalu tahu bahwa Anda akan menghancurkan timer di beberapa titik dan memutus siklus.

Dalam kasus saya barusan, saya mendapat peringatan untuk kode ini:

[x setY:^{ [x doSomething]; }];

Sekarang saya tahu bahwa dentang hanya akan menghasilkan peringatan ini jika mendeteksi metode dimulai dengan "set" (dan satu kasus khusus lainnya yang tidak akan saya sebutkan di sini). Bagi saya, saya tahu tidak ada bahaya jika ada retain loop, jadi saya mengubah nama metode menjadi "useY:" Tentu saja, itu mungkin tidak sesuai dalam semua kasus dan biasanya Anda ingin menggunakan referensi yang lemah, tetapi Saya pikir perlu dicatat solusi saya jika itu membantu orang lain.

Chris Suter
sumber
4

Banyak kali, ini sebenarnya bukan siklus mempertahankan .

Jika Anda tahu itu bukan, Anda tidak perlu membawa Diri yang lemah yang sia-sia ke dunia.

Apple bahkan memaksa peringatan ini kepada kami dengan API ke mereka UIPageViewController, yang mencakup metode set (yang memicu peringatan ini - seperti yang disebutkan di tempat lain - berpikir Anda menetapkan nilai ke sebuah ivar yang merupakan blok) dan blok handler penyelesaian (di mana Anda pasti akan merujuk pada diri Anda sendiri).

Berikut ini beberapa arahan kompiler untuk menghapus peringatan dari satu baris kode:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop
bshirley
sumber
1

Menambahkan dua sen untuk meningkatkan ketepatan dan gaya. Dalam kebanyakan kasus, Anda hanya akan menggunakan satu atau beberapa anggota selfdi blok ini, kemungkinan besar hanya untuk memperbarui slider. Casting selfitu berlebihan. Alih-alih, lebih baik untuk eksplisit dan melemparkan hanya objek yang benar-benar Anda butuhkan di dalam blok. Misalnya, jika ini merupakan contoh dari UISlider*, katakanlah, _timeSliderlakukan saja hal berikut sebelum deklarasi blok:

UISlider* __weak slider = _timeSlider;

Kemudian gunakan saja sliderdi dalam blok. Secara teknis ini lebih tepat karena mempersempit siklus mempertahankan potensial hanya untuk objek yang Anda butuhkan, tidak semua objek di dalam self.

Contoh lengkap:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Selain itu, kemungkinan besar objek yang dilemparkan ke pointer lemah sudah menjadi pointer lemah di dalam selfjuga meminimalkan atau menghilangkan sepenuhnya kemungkinan siklus penahan. Dalam contoh di atas, _timeSlidersebenarnya properti disimpan sebagai referensi yang lemah, misalnya:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

Dalam hal gaya pengkodean, seperti halnya dengan C dan C ++, deklarasi variabel lebih baik dibaca dari kanan ke kiri. Menyatakan SomeType* __weak variabledalam rangka ini berbunyi lebih alami dari kanan ke kiri sebagai: variable is a weak pointer to SomeType.

Luis Artola
sumber
1

Saya mengalami peringatan ini baru-baru ini dan ingin memahaminya sedikit lebih baik. Setelah sedikit trial and error, saya menemukan bahwa itu berasal dari memiliki metode mulai dengan "tambah" atau "simpan". Objective C memperlakukan nama metode yang dimulai dengan "baru", "mengalokasikan", dll sebagai mengembalikan objek yang dipertahankan tetapi tidak menyebutkan (bahwa saya dapat menemukan) apa pun tentang "tambah" atau "simpan". Namun, jika saya menggunakan nama metode dengan cara ini:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Saya akan melihat peringatan di baris [dilakukan sendiri]. Namun, ini tidak akan:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Saya akan pergi ke depan dan menggunakan cara "__weak __typeof (diri) lemahSelf = diri" untuk referensi objek saya tetapi benar-benar tidak suka harus melakukannya karena akan membingungkan masa depan saya dan / atau dev lainnya. Tentu saja, saya juga tidak bisa menggunakan "add" (atau "save") tapi itu lebih buruk karena menghilangkan arti dari metode ini.

Ray M.
sumber