Bir API uygularken bloklar halinde kendini yakalamayı nasıl önleyebilirim?


222

Çalışan bir uygulama var ve ben Xcode 4.2 ARC dönüştürmek üzerinde çalışıyorum. Ön kontrol uyarılarından biri, selftutma döngüsüne yol açan bir blokta güçlü bir şekilde yakalamayı içerir . Sorunu göstermek için basit bir kod örneği yaptım. Bunun ne anlama geldiğini anlıyorum ama bu tür bir senaryoyu uygulamak için "doğru" veya önerilen yoldan emin değilim.

  • self, MyAPI sınıfının bir örneğidir
  • Aşağıdaki kod, yalnızca sorumla ilgili nesneler ve bloklarla etkileşimleri göstermek için basitleştirildi
  • MyAPI'nin uzak bir kaynaktan veri aldığını ve MyDataProcessor'ın bu veriler üzerinde çalıştığını ve bir çıktı oluşturduğunu varsayalım
  • işlemci ilerleme durumunu bildirmek için bloklarla yapılandırıldı

kod örneği:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

Soru: "yanlış" olarak ne yapıyorum ve / veya ARC sözleşmelerine uyacak şekilde nasıl değiştirilmelidir?

Yanıtlar:


509

Kısa cevap

selfDoğrudan erişim yerine, saklanmayacak bir referanstan dolaylı olarak erişmelisiniz. Otomatik Referans Sayımı (ARC) kullanmıyorsanız , bunu yapabilirsiniz:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

__blockBloğun içine değiştirilebilir anahtar kelime işaretleri değişkenler (biz bu yapmıyoruz) ama (sen ARC kullanmıyorsanız) da otomatik blok muhafaza edildiğinde tutulmaz. Bunu yaparsanız, MyDataProcessor örneği serbest bırakıldıktan sonra başka hiçbir şeyin bloğu yürütmeye çalışmadığından emin olmalısınız. (Kodunuzun yapısı göz önüne alındığında, bu bir sorun olmamalı.) Hakkında daha fazla bilgi edinin__block .

ARC kullanıyorsanız , __blockdeğişikliklerin ve referansların semantiği korunacak, bu durumda bunu beyan etmelisiniz __weak.

Uzun cevap

Diyelim ki böyle bir kodunuz var:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

Buradaki sorun, benliğin bloğa bir referans tutmasıdır; bu arada blok, delege özelliğini almak ve delege'ye bir yöntem göndermek için kendine bir referans tutmalıdır. Uygulamanızdaki her şey bu nesneye referansını serbest bırakırsa, tutma sayısı sıfır olmaz (blok ona işaret ettiği için) ve blok yanlış bir şey yapmaz (nesne nesneyi işaret ettiği için) vb. nesneler çifti yığına sızacak, hafızayı işgal edecek, ancak bir hata ayıklayıcı olmadan sonsuza kadar ulaşılamayacak. Gerçekten trajik.

Bu durum bunun yerine kolaylıkla düzeltilebilir:

id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
    [progressDelegate processingWithProgress:percentComplete];
}

Bu kodda, benlik bloğu tutuyor, blok delege tutuyor ve döngü yok (buradan görülebilir; delege nesneyi koruyabilir, ancak şu anda bizim elimizden çıkmıyor). Delege özelliğinin değeri, yürütüldüğünde aramak yerine, blok oluşturulduğunda yakalandığından, bu kod aynı şekilde sızıntı riskini ortadan kaldırmaz. Bir yan etkisi, bu blok oluşturulduktan sonra delege değiştirirseniz, blok yine de eski delege için güncelleme iletileri gönderir olmasıdır. Bunun gerçekleşip gerçekleşmeyeceği uygulamanıza bağlıdır.

Bu davranıştan memnun olsanız bile, yine de bu hileyi sizin durumunuzda kullanamazsınız:

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

Burada selfdoğrudan yöntem çağrısında temsilciye geçiyorsunuz, bu yüzden oraya bir yere götürmelisiniz. Blok türünün tanımı üzerinde kontrolünüz varsa, en iyi şey, temsilciyi bloğa parametre olarak geçirmek olacaktır:

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

Bu çözüm, tutma döngüsünü önler ve her zaman geçerli temsilci çağırır.

Bloğu değiştiremezseniz, onunla başa çıkabilirsiniz . Koruma döngüsünün bir hata değil uyarı olması nedeni, uygulamanız için kıyameti mutlaka hecelememesidir. İşlem MyDataProcessortamamlandığında blokları serbest bırakabiliyorsa, ebeveyni serbest bırakmaya çalışmadan önce döngü bozulur ve her şey düzgün bir şekilde temizlenir. Bundan emin olabilirseniz, yapılacak doğru şey, #pragmabu kod bloğunun uyarılarını bastırmak için a kullanmak olacaktır . (Veya dosya başına derleyici bayrağı kullanın. Ancak tüm proje için uyarıyı devre dışı bırakmayın.)

Ayrıca, benzer bir hile kullanarak, zayıf veya el değmemiş bir referans bildirerek ve bunu blokta kullanarak da bakabilirsiniz. Örneğin:

__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

Yukarıdakilerin üçü de, sonucu tutmadan bir referans verecektir, ancak hepsi biraz farklı davranır: __weaknesne serbest bırakıldığında referansı sıfırlamaya çalışacaktır; __unsafe_unretainedsizi geçersiz bir işaretçiyle bırakacaktır; __blockaslında başka bir dolaylılık düzeyi ekler ve referansın değerini blok içinden değiştirmenize izin verir ( dpbaşka bir yerde kullanılmadığı için bu durumda alakasız ).

En iyisi , hangi kodu değiştirebileceğinize ve neleri değiştiremeyeceğinize bağlı olacaktır. Ama umarım bu size nasıl ilerleyeceğiniz konusunda bazı fikirler verir.


1
Müthiş cevap! Teşekkürler, neler olup bittiğini ve bunların nasıl çalıştığını daha iyi anlıyorum. Bu durumda, her şey üzerinde kontrolüm var, bu yüzden bazı nesneleri gerektiği gibi yeniden tasarlayacağım.
XJones

18
O_O Sadece biraz farklı bir problemle geçiyordum, okumaya takılı kaldım ve şimdi bu sayfayı tüm bilgili ve havalı hissediyorum. Teşekkürler!
Orc JMR

blok yürütme anından herhangi bir nedenle dpserbest bırakılacaksa (örneğin, bir görünüm denetleyicisiyse ve [dp.delegate ...açılmışsa ), satırın EXC_BADACCESS'e neden olacağı doğrudur.
Ocak'ta

Bloğu tutan özellik (ör. DataProcess.progress) olmalı strongweak?
djskinner

1
Sen bir göz olabilir libextobjc adlandırılan iki kullanışlı makro sağlar @weakify(..)ve @strongify(...)kullanmak sağlayan selfolmayan bir istinat moda bloğunda.

25

Gelecekte döngünün bozulacağından eminseniz uyarıyı bastırma seçeneği de vardır:

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

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

Bu şekilde __weak, selftakma ad ve açık ivar önekiyle maymunlaşmak zorunda kalmazsınız .


8
__Weak id ile değiştirilebilecek 3 satırdan fazla kod alan çok kötü bir uygulama gibi geliyor.
Ben Sinclair

3
Sıkıştırılmış uyarılardan faydalanabilecek daha büyük bir kod bloğu vardır.
zoul

2
Bunun dışında __weak id weakSelf = self;uyarıyı bastırmaktan temelde farklı davranışlar vardır. Soru "... tutma döngüsünün bozulacağından eminseniz" ile başladı
Tim

Çok sık insanlar, sonuçları gerçekten anlamadan, değişkenleri körü körüne zayıflatırlar. Örneğin, insanların bir nesneyi zayıflattığını gördüm ve sonra, blokta yaptıklarını: [array addObject:weakObject];zayıfObject serbest bırakıldıysa, bu bir çökmeye neden olur. Açıkçası bu, bir tutma döngüsü boyunca tercih edilmez. Bloğunuzun gerçekten zayıflamayı garanti edecek kadar uzun yaşayıp yaşamadığını ve bloktaki eylemin zayıf nesnenin hala geçerli olup olmadığına bağlı olup olmadığını anlamak zorundasınız.
mahboudz

14

Ortak bir çözüm için, bu derleme önceden derleme başlığında var. Yakalamayı önler ve kullanmaktan kaçınarak derleyici yardımını hala etkinleştiririd

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

Sonra kodda şunları yapabilirsiniz:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

Kabul etti, bu blok içinde bir soruna neden olabilir. ReactiveCocoa, bu sorun selfiçin @weakify (self) bloğunuzun içinde kullanmaya devam etmenizi sağlayan ilginç bir başka çözüme sahiptir ; id block = ^ {@strongify (kendi kendine); [self.delegate myAPIDidFinish: self]; };
Damien Pontifex

@dmpontifex bu libextobjc'nin bir makrosu github.com/jspahrsummers/libextobjc
Elechtron

11

ARC içermeyen çözümün ARC ile de çalıştığına inanıyorum __block:

EDIT: ARC Geçiş Notları Geçiş başına, __blockdepolama ile bildirilen bir nesne hala korunur. Kullanım __weak(tercih edilir) ya da __unsafe_unretained(geriye doğru uyumluluk için).

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

__blockAnahtar kelimenin başvurusunu korumayı engellediğini fark etmedim. Teşekkürler! Monolitik cevabımı güncelledim. :-)
benzado

3
Apple belgelerine göre "Manuel referans sayma modunda, __block id x; x'i tutma etkisi vardır. ARC modunda, __block id x; varsayılan olarak x'i tutmak (diğer tüm değerler gibi)."
XJones

11

Diğer birkaç cevabı birleştirerek, şimdi bloklarda kullanmak için yazılan zayıf bir benlik için kullandığım şey:

__typeof(self) __weak welf = self;

Ben sadece "biz" yazdıktan sonra isabet yöntemleri / fonksiyonlarda "welf" bir tamamlama öneki ile bir XCode Kod Parçacığı olarak ayarlayın .


Emin misiniz? Bu bağlantı, clang dokümanlar hem teneke düşünüyor ve nesnenin bir başvuru tutmak için kullanılması gereken ancak neden olur değil bir bağlantı, bir döngü muhafaza: stackoverflow.com/questions/19227982/using-block-and-weak
Kendall Helmstetter Gelner

Clang dokümanlarından: clang.llvm.org/docs/BlockLanguageSpec.html "Objective-C ve Objective-C ++ dillerinde, __block nesne tanımlayıcısının nesne türüne izin veririz. Çöp toplama etkin değilse, bu niteleyici neden olur bu değişkenler, iletiler saklanmadan saklanacak. "
Kendall Helmstetter Gelner


6

warning => "bloğun içinde kendini yakalamanın bir tutma döngüsüne öncülük etmesi muhtemeldir"

kendini veya özelliğini, yukarıdaki uyarıda göründüğünden daha fazla kendi kendine koruyan bir blok içinde ifade ettiğinizde.

kaçınmak için bir hafta ref yapmak zorundayız

__weak typeof(self) weakSelf = self;

yani kullanmak yerine

blockname=^{
    self.PROPERTY =something;
}

kullanmalıyız

blockname=^{
    weakSelf.PROPERTY =something;
}

not: retain döngüsü genellikle her ikisinin referans sayısı = 1 olan ve delloc yönteminin hiçbir zaman çağrılmadığı iki nesnenin birbirine nasıl atıldığı zaman oluşur.



-1

Kodunuzun bir tutma döngüsü oluşturmayacağından veya döngünün daha sonra bozulacağından eminseniz, uyarıyı susturmanın en basit yolu:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

[self dataProcessor].progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

[self dataProcessor].completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

Bunun çalışmasının nedeni, özelliklerin nokta erişiminin Xcode'un analizi tarafından dikkate alınması ve bu nedenle

x.y.z = ^{ block that retains x}

x öğesinin y (atamanın sol tarafında) ve y x (sağ tarafta) tarafından alıkonulduğu görülüyorsa, yöntem çağrıları özellik erişim yöntemi çağrıları olsa bile aynı analize tabi tutulmaz. bu özellik erişim yöntemleri derleyici tarafından oluşturulmuş olsa bile nokta erişimine eşdeğerdir.

[x y].z = ^{ block that retains x}

yalnızca sağ taraf bir tutucu (y x ile) oluşturur ve tutma döngüsü uyarısı oluşturulmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.