Bloklarla `` kendi '' döngüsünü koruma


167

Korkarım ki bu soru oldukça basit, ama bence bu bloklara giren birçok Objective-C programcısıyla alakalı.

Duyduğum şey, bloklar içinde constkopya olarak belirtilen yerel değişkenleri yakaladığından , selfbir blok içinde kullanmak blokun kopyalanması durumunda bir tutma döngüsüyle sonuçlanabilir. Dolayısıyla, __blockbloğu selfkopyalamak yerine doğrudan uğraşmaya zorlamak için kullanmamız gerekiyor .

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

sadece yerine

[someObject messageWithBlock:^{ [self doSomething]; }];

Bilmek istediğim şey şu: eğer bu doğruysa, çirkinliği önleyebileceğim bir yol var mı (GC kullanmanın yanı sıra)?


3
selfProxy'leri thissadece bir şeyleri ters çevirmek için çağırmayı seviyorum . JavaScript'te thiskapanışlarımı arıyorum self, bu yüzden güzel ve dengeli hissediyor. :)
devios1

Swift blokları kullanıyorsam, eşdeğer bir işlem yapılması gerektiğini merak ediyorum
Ben Lu

@BenLu kesinlikle! Swift'te kapanışlar (ve bu sözden dolaylı veya açık bir şekilde geçen işlevler) kendini koruyacaktır. Bazen bu istenir ve diğer zamanlarda bir döngü yaratır (çünkü kapağın kendisi kendine aittir (veya kendi sahip olduğu bir şeye aittir). Bunun ana nedeni ARC'dir.
Ethan

1
Sorunlardan kaçınmak için, bir blokta kullanılacak 'kendini' tanımlamanın uygun yolu '__typeof (self) __weak poorSelf = self;' zayıf bir referans için.
XLE_22

Yanıtlar:


169

Açıkçası, bunun bir const kopyası olması, bu sorunla ilgisi yoktur. Bloklar, oluşturulduklarında yakalanan obj-c değerlerini korur. Öyle ki const-copy sorunu için geçici çözüm, korunma sorunu için geçici çözüm ile aynıdır; yani, __blockdeğişken için depolama sınıfını kullanma .

Her durumda, sorunuzu cevaplamak için burada gerçek bir alternatif yok. Kendi blok tabanlı API'nizi tasarlıyorsanız ve bunu yapmak mantıklıysa, bloğun selfargüman olarak in değerini geçmesini sağlayabilirsiniz . Ne yazık ki, bu çoğu API için anlamlı değildir.

Bir ivar'a başvurmanın aynı sorunu yaşadığını lütfen unutmayın. Bloğunuzdaki bir ivar'a başvurmanız gerekirse, bunun yerine bir özellik kullanın veya kullanın bself->ivar.


Zeyilname: ARC olarak derlenirken, __blockartık kopma döngüleri kalmaz. ARC için derliyorsanız __weakveya __unsafe_unretainedyerine kullanmanız gerekir .


Sorun değil! Bu soru memnuniyetinize cevap verdiyse, bunu sorunuzun doğru cevabı olarak seçebilirseniz sevinirim. Değilse, lütfen sorunuzu daha iyi nasıl cevaplayabileceğimi bize bildirin.
Lily Ballard

4
Sorun değil, Kevin. SO, hemen bir soruya cevap seçmenizi geciktiriyor, bu yüzden biraz sonra geri gelmem gerekiyordu. Şerefe.
Jonathan Sterling

__unsafe_unretained id bself = benlik;
caleb

4
@JKLaiho: Elbette, __weakgayet iyi. Blok çağrıldığında nesnenin kapsam dışında olamayacağını biliyorsanız, o zaman __unsafe_unretainedbiraz daha hızlıdır, ancak genel olarak bu bir fark yaratmaz. Eğer kullanımını yaparsanız __weak, bir içine atmak için emin olun __strongo olmayan yerel değişken ve testin nilonunla bir şey yapmadan önce.
Lily Ballard

2
@Rpranata: Evet. __block'nin alıkoyma ve bırakmama yan etkisi sadece bununla ilgili uygun bir şekilde akıl yürütmeme nedeniyledir. ARC ile derleyici bu yeteneği kazandı ve __blockşimdi de koruyor ve serbest bırakıyor. Bundan kaçınmanız gerekiyorsa __unsafe_unretained, derleyiciye değişkenteki değer üzerinde herhangi bir tutma veya bırakma yapmamasını bildiren kullanmanız gerekir .
Lily Ballard

66

Sadece kullan:

__weak id weakSelf = self;

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

Daha fazla bilgi için: WWDC 2011 - Uygulamada Bloklar ve Grand Central Dispatch .

https://developer.apple.com/videos/wwdc/2011/?id=308

Not: Bu işe yaramazsa deneyebilirsiniz

__weak typeof(self)weakSelf = self;

2
Ve herhangi bir tesadüfen buldun mu :)?
Tieme

2
Videoyu buradan kontrol edebilirsiniz - developer.apple.com/videos/wwdc/2011/…
nanjunda

"SomeOtherMethod" içinde kendinize referans verebilir misiniz? Kendini bu noktada zayıflatıyor mu, yoksa bu bir tutma döngüsü yaratır mı?
Ören

Merhaba @Oren, "someOtherMethod" içinde kendinize başvurmaya çalışırsanız bir Xcode uyarısı alırsınız. Benim yaklaşımım kendime zayıf bir referans yapıyor.
3lvis

1
Sadece doğrudan bloğun içinde kendini referans alırken bir uyarı aldım. Kendini someOtherMethod içine koymak hiçbir uyarıya neden olmadı. Bunun nedeni, xcode yeterince akıllı olmadığı veya bir sorun olmadığı için mi? SomeOtherMethod içindeki kendini referans almak, zaten metodu çağırdığın için zaten zayıfSelf'e atıfta bulunur mu?
Ören

22

Bu açık olabilir, ancak sadece selfbir döngü elde edeceğinizi bildiğinizde çirkin takma ad yapmanız gerekir. Blok sadece tek seferlik bir şeyse, tutmayı güvenle göz ardı edebileceğinizi düşünüyorum self. Kötü durum, örneğin bloğu geri arama arabirimi olarak kullanmaktır. Burası gibi:

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

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

@implementation AudioProcessor

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

Burada API çok mantıklı değil, örneğin bir üst sınıfla iletişim kurarken anlamlıdır. Tampon tutucuyu tutuyoruz, tampon tutucusu bizi tutuyor. Böyle bir şeyle karşılaştırın:

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];
    }];
}

Bu durumlarda selftakma ad yapmam . Bir tutma döngüsü elde edersiniz, ancak işlem kısa ömürlüdür ve blok sonunda belleği bozar ve döngüyü kırar. Ancak bloklarla ilgili deneyimim çok küçük ve selförtüşme uzun vadede en iyi uygulama olarak ortaya çıkabilir .


6
İyi bir nokta. Bu sadece kendini bloğu canlı tutuyorsa bir koruma döngüsüdür. Asla kopyalanmayan bloklar veya garanti süresi sınırlı olan bloklar (örneğin, bir UIView animasyonu için tamamlama bloğu) söz konusu olduğunda endişelenmenize gerek yoktur.
Lily Ballard

6
Prensipte haklısın. Ancak, örnekte kodu yürütürseniz çökersiniz. Blok özellikleri olmalıdır daima olarak ilan edilmesi copydeğil, retain. Eğer sadece retainöyleyse, yığının dışına taşınacaklarının garantisi yoktur, yani onu yürütmeye gittiğinizde artık orada olmayacaktır. (ve kopyalanan ve zaten kopyalanan blok bir alıkoymak için optimize edilmiştir)
Dave DeLong

Ah, elbette, bir yazım hatası. retainBir süre önce aşamadan geçtim ve hızlı bir şekilde söylediğin şeyi fark ettim :) Teşekkürler!
zoul

retainBloklar için tamamen göz ardı edildiğinden eminim (daha önce yığından taşınmamışlarsa copy).
Steven Fisher

@Dave DeLong, Hayır, @property (retain) sadece nesne referansı için kullanıldığından, blok değil,
çökmedi

20

Başka bir cevap yayınlamak çünkü bu benim için de bir problemdi. Başlangıçta bir blok içinde kendini referans olan bir yerde blockSelf kullanmak zorunda olduğumu düşündüm. Durum böyle değil, sadece nesnenin kendisinde bir blok varsa. Ve aslında, bu durumda blockSelf kullanırsanız, nesne bloktan geri dönmeden önce nesne dağıtılabilir ve daha sonra çağırmaya çalıştığında çökecektir, bu yüzden açıkça cevaba kadar kendinizin korunmasını istiyorsunuz geri döner.

İlk durum, blokta başvurulan bir blok içerdiğinden bir tutma döngüsünün ne zaman gerçekleşeceğini gösterir:

#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];
}

İkinci durumda blockSelf'e ihtiyacınız yoktur, çünkü çağıran nesnede, kendinize başvurduğunuzda tutma döngüsüne neden olacak bir blok yoktur:

#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;
} 

Bu yaygın bir yanlış anlamadır ve tehlikeli olabilir, çünkü tutması gereken bloklar selfbu düzeltmeyi aşırı uygulayan insanlar nedeniyle olmayabilir. Bu, ARC olmayan kodda tutma döngülerinden kaçınmanın iyi bir örneğidir, yayınladığınız için teşekkürler.
Carl Veazey

9

Ayrıca, bloğunuz daha sonra tutulan başka bir nesneye başvuruyorsa tutma döngülerinin gerçekleşebileceğini unutmayın self.

Çöp Toplamanın bu muhafaza döngülerinde yardımcı olabileceğinden emin değilim. Bloğu tutan nesne (sunucu nesnesini çağıracağım self), (istemci nesnesi)self , bloğun içindeki , tutma nesnesinin kendisi serbest bırakılıncaya kadar döngüsel olarak kabul edilmez. Sunucu nesnesi istemcilerinden çok daha fazlaysa, önemli miktarda bellek sızıntısı olabilir.

Temiz bir çözüm olmadığı için aşağıdaki geçici çözümleri öneririm. Sorununuzu çözmek için bunlardan birini veya daha fazlasını seçmekten çekinmeyin.

  • Blokları yalnızca tamamlama için kullanın , açık uçlu etkinlikler için kullanmayın. Örneğin, gibi yöntemler için bloklar kullanın doSomethingAndWhenDoneExecuteThisBlock:;setNotificationHandlerBlock: . Tamamlama için kullanılan blokların belirli ömürleri vardır ve değerlendirildikten sonra sunucu nesneleri tarafından serbest bırakılmalıdır. Bu, tutma döngüsünün meydana gelse bile çok uzun süre yaşamasını önler.
  • Tanımladığınız zayıf referans dansı yapın.
  • Nesnenizi yayınlanmadan önce temizlemek için bir yöntem sağlayın; bu nesne, nesnenin kendisine referans olabilecek sunucu nesnelerinden "bağlantısını keser"; ve nesne üzerinde yayın çağırmadan önce bu yöntemi çağırın. Nesneniz yalnızca bir istemciye sahipse (veya bir bağlamda tekil) ise, ancak bu yöntem birden çok istemciye sahipse bozulursa, bu yöntem mükemmeldir. Temel olarak burada tutma sayma mekanizmasını yeniyorsunuz; deallocbunun yerine çağırmaya benzer release.

Bir sunucu nesnesi yazıyorsanız, yalnızca tamamlanmak üzere blok bağımsız değişkenleri alın. Gibi geri aramalar için engelleme argümanlarını kabul etmeyin setEventHandlerBlock:. Bunun yerine, klasik temsilci modeline geri dönün: resmi bir protokol oluşturun ve bir setEventDelegate:yöntemin reklamını yapın . Delege tutmayın. Resmi bir protokol oluşturmak bile istemiyorsanız, seçiciyi temsilci geri çağrısı olarak kabul edin.

Ve son olarak, bu model alarmları çalmalıdır:

- (geçersiz) dealloc {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}

selfİçeriden başvurabilecek blokları kancadan çıkarmaya çalışıyorsanız dealloc, zaten başınız belada demektir. deallocbloktaki referansların neden olduğu tutma döngüsü nedeniyle hiçbir zaman çağrılamayabilir; bu, nesnenizin sunucu nesnesi yeniden yerleştirilene kadar sızacağını gösterir.


__weakUygun şekilde kullanırsanız GC yardımcı olur .
tc.

Çöp toplama işleminin izlenmesi elbette tutma döngüleri ile ilgilenebilir. Döngüleri korumak yalnızca referans sayma ortamları için bir sorundur
newacct

Herkesin bildiği gibi, çöp toplama Otomatik Referans Sayma (ARC) lehine OS Xv10.8'de kullanımdan kaldırıldı ve OS X'in gelecekteki bir sürümünde kaldırılması planlanıyor ( developer.apple.com/library/mac/#releasenotes / ObjectiveC /… ).
Ricardo Sanchez-Saez

1

__block __unsafe_unretainedKevin'ın gönderisinde önerilen değiştiriciler , farklı bir iş parçacığında yürütülen blok durumunda kötü erişim istisnasına neden olabilir. Temp değişkeni için sadece __block değiştirici kullanmak ve kullanımdan sonra sıfır yapmak daha iyidir .

__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;
}];

Değişkeni kullandıktan sonra doldurma ihtiyacını ortadan kaldırmak için __block yerine __weak kullanmak gerçekten daha güvenli olmaz mıydı? Yani, diğer çözüm türlerini kırmak istiyorsanız bu çözüm harikadır, ancak kesinlikle "kendini" koruma döngüleri için belirli bir avantaj görmüyorum.
ale0xB

Platform hedefiniz iOS 4.x ise __weak kullanamazsınız. Ayrıca bazen bloktaki kodun nil için değil, geçerli nesne için yürütülmüş olması gerekir.
b1gbr0

1

Libextobjc kütüphanesini kullanabilirsiniz. Oldukça popüler, örneğin ReactiveCocoa'da kullanılıyor. https://github.com/jspahrsummers/libextobjc

2 makro @weakify ve @strongify sağlar, böylece sahip olabilirsiniz:

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

Bu doğrudan güçlü bir referansı engeller, bu nedenle kendiliğimiz için bir tutma döngüsüne girmeyiz. Ve ayrıca, benliğin yarıya kadar sıfırlanmasını önler, ancak yine de alıkoyma sayısını düzgün bir şekilde azaltır. Bu bağlantıdan daha fazlası: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
Basitleştirilmiş kodu göstermeden önce, arkasında ne olduğunu bilmek daha iyi olurdu, herkes gerçek iki kod satırını bilmelidir.
Alex Cio

0

Buna ne dersin?

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

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

Derleyici uyarısını artık almıyorum.


-1

Blok: blokta başvurulan bir blok içerdiğinden bir tutma döngüsü gerçekleşir; Blok kopyasını yapar ve bir üye değişkeni kullanırsanız, kendiliğinden kalır.

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.