Bir nesneye bağlı bir anahtar değer gözlemcisi olup olmadığını nasıl anlarım


142

bir objektif c nesnesine removeObservers'a söylerseniz: bir anahtar yol için ve bu anahtar yol kaydedilmemişse, üzüntüleri çatlatır. sevmek -

'Gözlemci olarak kaydedilmediğinden "KeyPath" anahtar yolu için bir gözlemci kaldırılamaz.'

bir nesne kayıtlı bir gözlemci olup olmadığını belirlemek için bir yolu var, bu yüzden bunu yapabilirsiniz

if (object has observer){
  remove observer
}
else{
  go on my merry way
}

Bu senaryoya, iOS 8'deki bir görünüm denetleyicisinin dağıtıldığı ve "Kaldırılamıyor" istisnasını attığı eski bir uygulamayı güncelledim. Ben arayarak düşündük addObserver:içinde viewWillAppear:ve buna removeObserver:yılında viewWillDisappear:, aramalar doğru eşleştirilmiş. Hızlı bir düzeltme yapmak zorundayım, bu yüzden try-catch çözümünü uygulayacağım ve sebebini daha fazla araştırmak için bir yorum bırakacağım.
bneely

Sadece benzer bir şeyle uğraşıyorum ve tasarımımı daha derinlemesine incelemem ve gözlemciyi tekrar kaldırmamam için ayarlamam gerektiğini görüyorum.
Bogdan

Bu cevapta önerildiği gibi bir bool değeri kullanmak benim için en iyisini yaptı: stackoverflow.com/a/37641685/4833705
Lance Samaria

Yanıtlar:


315

RemoveObserver çağrınızın etrafına bir deneme yakalama koyun

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}

12
1+ İyi cevap, benim için çalıştı ve düzenlenmeden önce rantınıza katılıyorum.
Robert

25
muhtemelen katılıyorum silinmiş rant için oy verdim.
Ben Gotow

12
Burada başka zarif bir çözüm yok mu? bu kullanım başına en az 2 ms sürer ... bir tableviewcell hayal
João Nunes

19
Bu, üretim kodu için güvensiz olduğunu ve herhangi bir zamanda başarısız olması gerektiğini söylemediğiniz için reddedildi. Çerçeve kodu ile istisnalar oluşturmak Kakao'da bir seçenek değildir.
Nikolai Ruhe

6
Bu kodun hızlı bir şekilde kullanılması 2.1. uyarı alırken {try self.playerItem? .removeObserver (self, forKeyPath: "status")} catch let hatasına neden olur.
Vipulk617

37

Asıl soru, onu gözlemleyip gözlemlemediğinizi neden bilmediğinizdir.

Bunu gözlenen nesnenin sınıfında yapıyorsanız, durun. Ne gözlemliyorsa onu izlemeye devam etmeyi umuyor. Gözlemcinin bildirimlerini bilgisi olmadan keserseniz, işlerin kırılmasını bekleyin; daha spesifik olarak, daha önce gözlemlenen nesneden güncelleme almadığı için gözlemcinin durumunun bayatlamasını bekleyin.

Bunu gözlem nesnesinin sınıfında yapıyorsanız, hangi nesneleri gözlemlediğinizi hatırlayın (ya da yalnızca bir nesneyi gözlemlerseniz, gözlemleyip gözlemlemediğinizi). Bu, gözlemin dinamik olduğunu ve birbiriyle alakasız iki nesne arasında olduğunu varsayar; gözlemci gözlenene sahipse, gözlemleneni oluşturduktan veya koruduktan sonra gözlemciyi ekleyin ve gözlemleneni serbest bırakmadan önce gözlemciyi çıkarın.

Bir nesneyi gözlemci olarak eklemek ve kaldırmak genellikle gözlemci sınıfında olmalı ve asla gözlemlenen nesnede olmamalıdır.


14
Kullanım Durumu: viewDidUnload'daki ve ayrıca dealloc'daki gözlemcileri kaldırmak istiyorsunuz. Bu, onları iki kez kaldırır ve viewController'ınız bir bellek uyarısından çıkarılmışsa ve daha sonra serbest bırakılırsa istisna atar. Bu senaryoyu nasıl ele almayı önerirsiniz?
bandejapaisa

2
@bandejapaisa: Cevabımda söylediğim hemen hemen: Gözlemleyip gözlemlemediğimi takip edin ve yalnızca öyleyse gözlemlemeyi bırakmaya çalışın.
Peter Hosey

41
Hayır, bu ilginç bir soru değil. Bunu takip etmek zorunda değilsiniz; eklenen yerdeki kod yoluna çarpıp çarpmadığınızı umursamadan, dealloc'daki tüm dinleyicilerin kaydını silmeniz gerekir. NSNotificationCenter'ın removeObserver'ı gibi çalışmalıdır; bu, aslında bir tane olup olmadığınızı umursamaz. Bu istisna, basitçe, başka hiçbir şeyin olmayacağı, kötü API tasarımı olan hatalar oluşturmaktır.
Glenn Maynard

1
@GlennMaynard: Cevabımda söylediğim gibi, “Gözlemcinin bildirimlerini bilgisi olmadan keserseniz, işlerin kırılmasını bekleyin; daha spesifik olarak, daha önce gözlemlenen nesneden güncelleme almadığı için gözlemcinin durumunun bayatlamasını bekleyin. ” Her gözlemci kendi gözlemini bitirmelidir; bunu yapamamak ideal olarak oldukça görünür olmalıdır.
Peter Hosey

3
Sorudaki hiçbir şey diğer kodların gözlemcilerini kaldırmaktan bahsetmiyor .
Glenn Maynard

25

FWIW, [someObject observationInfo]gibi görünüyor nileğer someObjectherhangi gözlemciler yoktur. Ancak bu davranışa güvenmediğim için belgelendiğini görmedim. Ayrıca, observationInfobelirli gözlemcileri almak için nasıl okunacağımı bilmiyorum .


Belirli bir gözlemciyi nasıl alabileceğimi biliyor musun? objectAtIndex:istenen sonucu vermez.)
Eimantas

1
@MattDiPasquale obsationInfo kodunu nasıl okuyabileceğimi biliyor musunuz? Baskılarda iyi çıkıyor, ancak geçersiz kılmak için bir işaretçi. Nasıl okumalıyım?
neeraj

obsationInfo, Xcode'un hata ayıklama belgesinde (başlıkta "büyü" olan bir şey) belgelenmiş hata ayıklama yöntemidir. Onu aramayı deneyebilirsin. Birinin nesnenizi gözlemleyip gözlemlemediğini bilmeniz gerekiyorsa - yanlış bir şey yaptığınızı söyleyebilirim. Mimarinizi ve mantığınızı yeniden düşünün. Zor yoldan öğrendim.)
Eimantas

Kaynak:NSKeyValueObserving.h
nefarianblack

artı 1 komik bir çıkmaz için ama yine de biraz yararlı cevap
Will Von Ullrich

4

Bunu yapmanın tek yolu, bir gözlemci eklediğinizde bir bayrak ayarlamaktır.


3
Her yerde BOOL'larla sonuçlanırsanız, gözlemciyi eklemeyi ve kaldırmayı işleyen bir KVO sarma nesnesi oluşturmalısınız. Gözlemcinizin yalnızca bir kez kaldırılmasını sağlayabilir. Bunun gibi bir nesne kullandık ve işe yarıyor.
bandejapaisa

her zaman gözlemlemiyorsanız harika bir fikir.
Andre Simon

4

Bir nesneye bir gözlemci eklediğinizde, bunu bir nesneye ekleyebilirsiniz NSMutableArray:

- (void)addObservedObject:(id)object {
    if (![_observedObjects containsObject:object]) {
        [_observedObjects addObject:object];
    }
}

Nesneleri gözetmek istemiyorsanız aşağıdaki gibi bir şey yapabilirsiniz:

for (id object in _observedObjects) {
    if ([object isKindOfClass:[MyClass class]]) {
        MyClass *myObject = (MyClass *)object;
        [self unobserveMethod:myObject];
    }
}
[_observedObjects removeAllObjects];

Unutmayın, tek bir nesneyi görmezden gelirseniz, nesneyi _observedObjectsdiziden kaldırın :

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}

1
Bu çok iş parçacıklı bir dünyada gerçekleşirse, dizinizin ThreadSafe olduğundan emin olmanız gerekir
shrutim

Nesneye, listeye her nesne eklendiğinde alıkoyma sayısını artıracak ve başvurusu diziden kaldırılmadığı sürece yeniden yerleştirilmeyecek güçlü bir nesne başvurusu yapıyorsunuz. Ben zayıf referansları tutmak için NSHashTable/ kullanmayı tercih ederim NSMapTable.
atulkhatri

3

Bence - bu, retainCount mekanizmasına benzer şekilde çalışır. Şu anda gözlemcinize sahip olduğunuzdan emin olamazsınız. Kontrol etseniz bile: self.observationInfo - gelecekte gözlemcilere sahip olacağınızdan / olmayacağınızdan emin olamazsınız.

Gibi retainCount . Belki obsationInfo yöntemi tam olarak bu tür bir işe yaramaz değil, sadece hata ayıklama amacıyla kullanıyorum.

Sonuç olarak - bunu bellek yönetiminde olduğu gibi yapmanız gerekir. Bir gözlemci eklediyseniz - ihtiyacınız olmadığında kaldırın. ViewWillAppear / viewWillDisappear vb. Yöntemleri kullanmak gibi. Örneğin:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addObserver:nil forKeyPath:@"" options:NSKeyValueObservingOptionNew context:nil];
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self removeObserver:nil forKeyPath:@""];
}

Ve bazı özel kontrollere ihtiyacınız var - bir dizi gözlemciyi işleyen kendi sınıfınızı uygulayın ve kontrolleriniz için kullanın.


[self removeObserver:nil forKeyPath:@""]; daha önce gitmesi gerekiyor: [super viewWillDisappear:animated];
Joshua Hart

@JoshuaHart neden?
quarezz

Çünkü bu bir parçalama yöntemidir (dealloc). Bir çeşit sökme yöntemini geçersiz kıldığınızda, süper son olarak adlandırırsınız. Gibi: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

viewWillDisapear bir parçalama yöntemi değildir ve dealloc ile bağlantısı yoktur. Gezinti yığınına ileri doğru iterseniz , viewWillDisapear çağrılır, ancak görünümünüz bellekte kalır. Kurulum / sökme mantığıyla nereye gittiğini görüyorum, ancak burada yapmak gerçek bir fayda sağlamayacak. Süper önce kaldırmayı yalnızca temel sınıfta geçerli gözlemci ile çakışabilecek bir mantığınız varsa yerleştirmek istersiniz.
quarezz

3

[someObject observationInfo]nilgözlemci yoksa geri dönün .

if ([tableMessage observationInfo] == nil)
{
   NSLog(@"add your observer");
}
else
{
  NSLog(@"remove your observer");

}

Apple belgelerine göre: obsationInfo, alıcıya kayıtlı tüm gözlemciler hakkındaki bilgileri tanımlayan bir işaretçi döndürür.
FredericK

Bu daha iyi söylendi @ mattdipasquale cevabı
Ben Leggiero

2

Gözlemci modelinin bütün noktası, gözlemlenen bir sınıfın "mühürlenmesine" izin verilmesidir - gözlemlenip gözlemlenmediğini bilmemek veya önemsemek. Bu kalıbı açıkça kırmaya çalışıyorsunuz.

Neden?

Yaşadığınız sorun, siz olmadığınızda gözlemlendiğinizi varsayıyor olmanızdır. Bu nesne gözleme başlamadı. Sınıfınızın bu işlemi kontrol etmesini istiyorsanız bildirim merkezini kullanmayı düşünmelisiniz. Bu şekilde, sınıfınızın verinin ne zaman izlenebileceği üzerinde tam kontrolü vardır. Bu nedenle, kimin izlediği umurumda değil.


10
Dinleyicinin bir şeyi dinleyip dinlemediğini nasıl öğrenebileceğini soruyor , gözlemlenen nesnenin gözlenip gözlemlenmediğini nasıl bulamayacağını değil.
Glenn Maynard

1

Ben çoğu zaman ne i bu sınıf içinde belirli bir bildirim için bir abone ol ve abonelikten çıkma yöntemi oluşturmak olmasıdır bu çözüm yakalamak bir hayranı değilim. Örneğin, bu iki yöntem, nesneyi genel klavye bildiriminin altına yazar veya abonelikten çıkarır:

@interface ObjectA : NSObject
-(void)subscribeToKeyboardNotifications;
-(void)unsubscribeToKeyboardNotifications;
@end

Bu yöntemler içinde aşağıdaki gibi abonelik durumuna bağlı olarak doğru veya yanlış olarak ayarlanmış bir özel özellik kullanın:

@interface ObjectA()
@property (nonatomic,assign) BOOL subscribedToKeyboardNotification
@end

@implementation

-(void)subscribeToKeyboardNotifications {
    if (!self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = YES;
    }
}

-(void)unsubscribeToKeyboardNotifications {
    if (self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = NO;
    }
}
@end

0

Adem'in cevabına ek olarak, bunun gibi bir makro kullanmayı önermek istiyorum

#define SafeRemoveObserver(sender, observer, keyPath) \
@try{\
   [sender removeObserver:observer forKeyPath:keyPath];\
}@catch(id anException){\
}

kullanım örneği

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}

1
Bir istisna atması ne kadar çılgınca? Hiçbir şey takılmazsa neden hiçbir şey yapmıyor?
Aran Mulholland
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.