Perbaiki peringatan "Menangkap [objek] dengan kuat di blok ini cenderung mengarah ke siklus penahan" dalam kode yang diaktifkan ARC

141

Dalam kode yang diaktifkan ARC, bagaimana cara memperbaiki peringatan tentang siklus mempertahankan potensial, ketika menggunakan API berbasis blok?

Peringatan:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

dihasilkan oleh cuplikan kode ini:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
    // ...
    }];

Peringatan terkait dengan penggunaan objek requestdi dalam blok.

Guillaume
sumber
1
Anda mungkin harus menggunakan responseDataalih-alih rawResponseData, periksa dokumentasi ASIHTTPRequest.
0xced

Jawaban:

165

Membalas diri sendiri:

Pemahaman saya tentang dokumentasi mengatakan bahwa menggunakan kata kunci blockdan mengatur variabel ke nol setelah menggunakannya di dalam blok seharusnya ok, tetapi masih menunjukkan peringatan.

__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    request = nil;
// ....

    }];

Pembaruan: membuatnya berfungsi dengan kata kunci '_ lemah' alih-alih ' _block', dan menggunakan variabel sementara:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    // ...
    }];

Jika Anda juga ingin menargetkan iOS 4, gunakan __unsafe_unretainedsebagai ganti __weak. Perilaku yang sama, tetapi pointer tetap menggantung bukannya secara otomatis diatur ke nol ketika objek dihancurkan.

Guillaume
sumber
8
Berdasarkan dokumen ARC, sepertinya Anda harus menggunakan __unsafe_unretained __block bersama untuk mendapatkan perilaku yang sama seperti sebelumnya saat menggunakan ARC dan blok.
Hunter
4
@SeanClarkHess: Ketika saya menggabungkan dua baris pertama, saya mendapatkan peringatan ini: "Menetapkan objek yang dipertahankan ke variabel lemah; objek akan dirilis setelah penugasan"
Guillaume
1
@Guillaume terima kasih atas tanggapannya, beberapa cara saya mengabaikan variabel sementara, mencobanya dan peringatannya hilang. Apakah Anda tahu mengapa ini berhasil? Apakah itu hanya menipu kompiler untuk menekan peringatan atau apakah peringatan itu sebenarnya tidak lagi berlaku?
Chris Wagner
2
Saya telah mengirimkan pertanyaan tindak lanjut: stackoverflow.com/questions/8859649/…
barfoon
3
Dapatkah seseorang menjelaskan mengapa Anda memerlukan kata kunci __block dan __weak? Saya kira ada siklus penahanan yang sedang dibuat, tetapi saya tidak melihatnya. Dan bagaimana cara membuat variabel sementara memperbaiki masalah?
user798719
50

Masalah terjadi karena Anda menetapkan blok untuk meminta yang memiliki referensi kuat untuk meminta di dalamnya. Blokir akan secara otomatis mempertahankan permintaan, sehingga permintaan asli tidak akan membatalkan alokasi karena siklus. Masuk akal?

Ini hanya aneh karena Anda menandai objek permintaan dengan __block sehingga itu dapat merujuk pada dirinya sendiri. Anda dapat memperbaikinya dengan membuat referensi yang lemah di sampingnya .

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
sumber
__hemat ASIHTTPRequest * wrequest = request; tidak bekerja untuk saya. Memberikan kesalahan saya menggunakan __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Ini menyebabkan karena mempertahankan diri di blok. Blok akan diakses dari diri sendiri, dan diri disebut dalam blok. ini akan membuat siklus tetap.

Coba selesaikan ini dengan membuat referensi yang lemah self

__weak typeof(self) weakSelf = self;

operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    [weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];
Pengembang HD
sumber
Ini adalah jawaban yang benar dan harus dicatat
Benjamin
6

Beberapa kali kompiler xcode memiliki masalah untuk mengidentifikasi siklus retain, jadi jika Anda yakin bahwa Anda tidak mempertahankan completionBlock Anda dapat meletakkan flag kompiler seperti ini:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}
GOrozco58
sumber
1
Beberapa mungkin berpendapat bahwa itu adalah desain yang buruk tetapi saya kadang-kadang membuat objek independen yang nongkrong di memori sampai mereka selesai dengan tugas yang tidak sinkron. Mereka dipertahankan oleh properti completBlock yang berisi referensi kuat untuk diri sendiri, menciptakan siklus mempertahankan disengaja. The completionBlock berisi self.completionBlock = nil, yang melepaskan completBlock dan memutus siklus retain, memungkinkan objek untuk dilepaskan dari memori setelah tugas selesai. Jawaban Anda berguna untuk membantu meredam peringatan yang terjadi ketika saya melakukan ini.
hyperspasm
1
sejujurnya, peluang seseorang menjadi benar dan penyusunnya salah sangat kecil. Jadi saya katakan bahwa melampaui peringatan adalah bisnis yang berisiko
Max MacLeod
3

Ketika saya mencoba solusi yang disediakan oleh Guillaume, semuanya baik-baik saja dalam mode Debug tetapi macet dalam mode Rilis.

Catatan yang tidak menggunakan __weak tetapi __unsafe_unretained karena target saya adalah iOS 4.3.

Kode saya macet saat setCompletionBlock: dipanggil pada objek "permintaan": permintaan dibatalkan alokasi ...

Jadi, solusi ini berfungsi baik dalam mode Debug dan Rilis:

// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code

self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;

[self.request setCompletionBlock:^
{
    [dataModel processResponseWithData:dataModel.request.receivedData];        
}];
squall2022
sumber
Solusi menarik. Apakah Anda mengetahui mengapa crash dalam mode rilis dan tidak di Debug?
Valerio Santinelli
2
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

}];

apa perbedaan antara referensi __weak dan __block?

Emil Marashliev
sumber