Kendini zayıf referansını her zaman ARC'de bloğa mı geçireceksin?


252

Objective-C'de blok kullanımı konusunda biraz kafam karıştı. Şu anda ARC kullanıyorum ve uygulamamda çok fazla blok var, şu anda selfzayıf referansı yerine her zaman atıfta bulunuyor . Bu blokların selftutulup tutulmalarının nedeni bu olabilir mi? Soru, bir blokta her zaman bir weakreferans kullanmalı selfmıyım?

-(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);
    }
}

Bu konuyla ilgili derinlemesine bir söylem istiyorsanız, dhoerl.wordpress.com/2013/04/23/…
David H

Yanıtlar:


721

Tartışmaya strongveya weaktartışmaya odaklanmamaya yardımcı olur . Bunun yerine döngü kısmına odaklanın .

Bir tutma döngüsü , A Nesnesi B Nesnesi'ni tuttuğunda ve B Nesnesi A Nesnesi'ni koruduğunda gerçekleşen bir döngüdür . Bu durumda, iki nesneden biri serbest bırakılırsa:

  • A Nesnesi yeniden konumlandırılmaz çünkü B Nesnesi buna bir referans tutar.
  • Ancak A nesnesinin bir referansı olduğu sürece B Nesnesi hiçbir zaman yeniden konumlandırılmaz.
  • Ancak A Nesnesi hiçbir zaman yeniden konumlandırılmaz çünkü B Nesnesi buna bir referans tutar.
  • sonsuza dek

Bu nedenle, bu iki nesne, programın ömrü boyunca, her şey düzgün çalışıyorsa, yeniden konumlandırılsa bile, bellekte takılır.

Yani, endişelendiğimiz şey döngüleri korumaktır ve kendi içinde ve bu döngüleri oluşturan bloklar hakkında hiçbir şey yoktur. Bu bir sorun değil, örneğin:

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

Blok korur self, ancak bloğu selftutmaz. Biri veya diğeri serbest bırakılırsa, hiçbir döngü oluşturulmaz ve her şey gerektiği gibi yeniden yerleştirilir.

Başın belaya girdiği yer şudur:

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

Şimdi, object ( self) öğeniz strongbloğa açıkça başvuruyor. Ve bloğun örtük güçlü bir referansı var self. Bu bir döngü ve şimdi her iki nesne de doğru şekilde yerleştirilmeyecek.

Bunun gibi bir durumda, self tanım gereği zaten strongbloğa bir referansı olduğu selfiçin, bloğun kullanması için açıkça zayıf bir referans yaparak çözülmesi genellikle en kolay olanıdır :

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

Ancak bu , çağıran bloklarla uğraşırken izlediğiniz varsayılan desen olmamalıdırself ! Bu yalnızca, benlik ve blok arasında bir tutma döngüsünün kırılması için kullanılmalıdır. Bu modeli her yerde benimseyecek olsaydınız, yeniden yerleştirildikten sonra yürütülen bir şeye bir blok geçirme riskiyle karşı karşıya kalırdınız self.

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

2
A alıkoyma B, B alıkoyma sonsuz bir döngü yapacak emin değilim. Referans sayısı açısından, A ve B'nin referans sayısı 1'dir. Bu durum için bir tutma döngüsünün nedeni, A ve B'nin dışında güçlü bir referansı olmayan başka bir grubun olmamasıdır - bu iki nesneye ulaşamayacağımız anlamına gelir (biz A'yı B'yi serbest bırakmak için kontrol edemez ve tersi), bunun için A ve B, her ikisini de hayatta tutmak için birbirlerine referans verir.
Danyun Liu

@Danyun A ve B arasındaki bir tutma döngüsünün , bu nesnelere yapılan tüm diğer referanslar serbest bırakılıncaya kadar kurtarılamaz olduğu doğru olsa da , bu onu daha az bir döngü yapmaz. Tersine, belirli bir döngü kurtarılabilir olabileceği için, kodunuzda bulunmanın uygun olmadığı anlamına gelmez. Tutma döngüleri kötü tasarım kokusu.
22'de jemmons

@jemmons Evet, her zaman olabildiğince bir tutma döngüsü tasarımından kaçınmalıyız.
Danyun Liu

1
@ Master Söylemem imkansız. Tamamen -setCompleteionBlockWithSuccess:failure:yönteminizin uygulanmasına bağlıdır . Ancak paginator, sahibi varsa ViewControllerve bu bloklar sonra çağrılmazsa ViewController, bir __weakreferans kullanmak güvenli bir hareket olacaktır (çünkü selfblokların sahibi olan şeye sahiptir ve bloklar çağırdığında hala etrafta olması muhtemeldir. tutmasalar bile). Ama bu bir sürü "if" s. Bu gerçekten ne yapılması gerektiğine bağlı.
jemmons

1
@Jai Hayır, ve bu bloklar / kapanışlarla ilgili bellek yönetimi sorununun merkezinde. Nesnelerin sahibi hiçbir şey olmadığında nesneler yer değiştirir. MyObjectve SomeOtherObjecther ikisi de bloğa sahiptir. İle bloğun referans geri çünkü MyObjectolduğunu weak, blok değil kendi MyObject. Blok sürece varolmaya garanti Yani iken ya MyObject ya SomeOtherObject mevcut, garantisi yok MyObjectsürece blok yaptığı gibi var olacaktır. MyObjecttamamen yeniden SomeOtherObjectkonumlandırılabilir ve hala olduğu sürece blok hala orada olacaktır.
jemmons

26

Her zaman zayıf bir referans kullanmanız gerekmez. Bloğunuz korunmaz, ancak yürütülür ve sonra atılırsa, bir tutma döngüsü oluşturmayacağı için kendini güçlü bir şekilde yakalayabilirsiniz. Bazı durumlarda, bloğun tamamlanıncaya kadar bloğun kendiliğinden kalmasını istersiniz, böylece erken yerleşmez. Bununla birlikte, bloğu güçlü bir şekilde yakalarsanız ve yakalama kendi içinde, bir tutma döngüsü oluşturur.


Sadece bloğu geri arama olarak yürütüyorum ve kendimin hiç uğraşmasını istemem. Ama sanki koruma döngüleri oluşturuyormuşum gibi görünüyor çünkü söz konusu View Controller kontrol edilmiyor ...
the_critic

1
Örneğin, görünüm denetleyicisi tarafından tutulan bir çubuk düğmesi öğeniz varsa ve bu blokta kendini güçlü bir şekilde yakalarsanız, bir tutma döngüsü olacaktır.
Leo Natan

1
@MartinE. Sorunuzu kod örnekleri ile güncellemelisiniz. Leo, oldukça güçlüdür (+1), mutlaka güçlü referans döngüsüne neden olmaz, ancak bu blokları nasıl kullandığınıza bağlı olarak değişebilir. Kod parçacıkları sağlarsanız size yardımcı olmak daha kolay olacaktır.
Rob

@LeoNatan Tutma döngüleri kavramını anlıyorum, ancak bloklarda ne olduğundan tam olarak emin değilim, bu yüzden beni biraz karıştırıyor
the_critic

Yukarıdaki kodda, işlem tamamlandığında ve blok serbest bırakıldığında kendi durumunuz serbest bırakılacaktır. Blokların nasıl çalıştığını ve kapsamlarında neyi ve ne zaman yakaladıklarını okumalısınız.
Leo Natan

26

@Jemmons ile tamamen katılıyorum:

Ancak bu, kendini çağıran bloklarla uğraşırken izlediğiniz varsayılan desen olmamalıdır! Bu yalnızca, benlik ve blok arasında bir tutma döngüsünün kırılması için kullanılmalıdır. Bu modeli her yerde benimseyecek olsaydın, benlik yere yerleştikten sonra yürütülen bir şeye bir blok geçirme riskiyle karşı karşıya kalırdın.

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

Bu sorunun üstesinden gelmek için weakSelf, bloğun içinde güçlü bir referans tanımlanabilir :

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
StrongSelf, zayıfSelf için referans sayısını arttırmaz mı? Böylece bir tutma döngüsü oluşturmak?
mskw

12
Tutma döngüleri, yalnızca nesnenin statik durumunda varsa önemlidir. Kod yürütülürken ve durumu akış halindeyken, birden çok ve muhtemelen yedekli tutma iyi durumdadır. Her neyse, bu modelle ilgili olarak, güçlü bir referans yakalamak, blok çalışmadan önce kendiliğinden yerleşme durumu için hiçbir şey yapmaz, bu hala olabilir. Bloğu yürütürken kendiliğinden yerleşmemesini sağlar . Bu, bloğun kendiliğinden işlem yapmaması durumunda bunun gerçekleşmesi için bir pencere vermesi durumunda önemlidir.
Pierre Houston

@smallduck, açıklamanız harika. Şimdi bunu daha iyi anlıyorum. Kitaplar bunu kapsamıyor, teşekkürler.
Huibin Zhang

5
Bu, strongSelf'in iyi bir örneği değildir, çünkü strongSelf'in açık bir şekilde eklenmesi, çalışma zamanının zaten yapacağı şeydir: doSomething satırında, yöntem çağrısı boyunca güçlü bir referans alınır. ZayıfKendisi zaten geçersiz kılınmışsa, güçlü ref sıfırdır ve yöntem çağrısı no-op'dur. StrongSelf'in yardımcı olduğu yerde, bir dizi işleminiz varsa veya bir üye alanına ( ->) erişiyorsanız , geçerli bir referans aldığınızdan emin olmak ve bunu tüm işlem setinde sürekli olarak tutmak istediğinizde, örneğinif ( strongSelf ) { /* several operations */ }
Ethan

19

Leo'nun işaret ettiği gibi, sorunuza eklediğiniz kod güçlü bir referans döngüsü (diğer bir deyişle, tutma döngüsü) önermez. Güçlü bir referans döngüsüne neden olabilecek operasyonla ilgili bir sorun, operasyonun serbest bırakılmaması olabilir. Kod snippet'iniz, işleminizi eşzamanlı olarak tanımlamamanızı önermekle birlikte, daha önce hiç göndermediyseniz isFinishedveya dairesel bağımlılıklarınız varsa veya böyle bir şey olsaydı serbest bırakılmaz . İşlem serbest bırakılmazsa, görünüm denetleyicisi de serbest bırakılmaz. Bir kesme noktası veya NSLogişleminizin deallocyöntemine eklemenizi ve bunun çağrıldığını onaylamanızı öneririm .

Dedin:

Tutma döngüleri kavramını anlıyorum, ancak bloklarda ne olacağından emin değilim, bu yüzden beni biraz karıştırıyor

Bloklarla oluşan tutma döngüsü (güçlü referans döngüsü) sorunları, bildiğiniz saklama döngüsü sorunları gibidir. Bir blok, blok içinde görünen herhangi bir nesneye güçlü referanslar sağlar ve blok serbest bırakılıncaya kadar bu güçlü referansları serbest bırakmaz. Bu nedenle, blok referansları self, hatta sadece selfkendine güçlü bir referansı koruyacak bir örnek değişkenini referans alırsa , blok serbest bırakılana kadar (veya bu durumda NSOperationalt sınıf serbest bırakılana kadar) çözülmez .

Daha fazla bilgi için, Objective-C: Bloklarla Çalışma belgesinin Kendini Yakalarken Güçlü Referans Döngülerinden Kaçının bölümüne bakın .

Görünüm denetleyiciniz hala serbest bırakılmıyorsa, çözülmemiş güçlü referansın nerede olduğunu tanımlamanız gerekir (bunun konumlandırıldığını doğruladığınızı varsayarak NSOperation). Yaygın bir örnek, tekrarın kullanılmasıdır NSTimer. Ya delegateda hatalı bir şekilde strongreferansı koruyan bazı özel veya başka bir nesne . Enstrümanları, nesnelerin güçlü referanslarını nereden aldıklarını izlemek için sık sık kullanabilirsiniz, örneğin:

Xcode 6'da referans sayılarını kaydetme

Veya Xcode 5'te:

Xcode 5'te referans sayılarını kaydetme


1
Başka bir örnek, işlemin blok oluşturucuda tutulması ve tamamlandığında serbest bırakılmamasıdır. +1 güzel yazma!
Leo Natan

@LeoNatan Kabul etti, ancak kod snippet'i onu ARC kullanıyorsa serbest bırakılacak yerel bir değişken olarak temsil ediyor. Ama haklısın!
Rob

1
Evet, OP'nin diğer cevapta talep ettiği gibi sadece bir örnek veriyordum.
Leo Natan

Bu arada, Xcode 8, serbest bırakılmamış nesnelere güçlü referanslar bulmanın daha kolay bir yolu olan "Hata Ayıklama Grafiği" ne sahiptir. Bkz. Stackoverflow.com/questions/30992338/… .
Rob

0

Bazı açıklamalar tutma döngüsü ile ilgili bir koşulu yok sayar [Bir grup nesne güçlü ilişkiler çemberi ile bağlıysa, grubun dışından güçlü referanslar olmasa bile birbirlerini canlı tutarlar.] Daha fazla bilgi için belgeyi okuyun


-2

Bloğun içindeki benliği şu şekilde kullanabilirsiniz:

// bloğun çağrısı

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

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


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