Amaç-C: NSNotification için gözlemci nereden kaldırılır?


102

Objektif bir C sınıfım var. İçinde bir init yöntemi oluşturdum ve içinde bir NSNotification kurdum

//Set up NSNotification
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(getData)
                                             name:@"Answer Submitted"
                                           object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self]Bu sınıfta nereye ayarlarım ? Biliyorum ki a için UIViewController, onu viewDidUnloadyönteme ekleyebilirim. Öyleyse, sadece bir hedef c Sınıfı oluşturduysam ne yapılması gerekiyor?


Dealloc yöntemine koydum.
onnoweb

1
Hedef c sınıfını oluşturduğumda dealloc yöntemi benim için otomatik olarak oluşturulmadı, bu yüzden onu eklemem uygun mu?
Zhen

Evet, uygulayabilir -(void)deallocve sonra ekleyebilirsiniz removeObserser:self. Koymak için en çok önerilen yol budurremoveObservers:self
petershine

deallocYöntemi iOS 6'ya koymak hala uygun mu?
wcochran

2
Evet, [super dealloc] 'u çağırmadığınız sürece ARC projelerinde dealloc kullanmanız uygundur ([super dealloc]' u çağırırsanız bir derleyici hatası alırsınız). Ve evet, removeObserver'ınızı kesinlikle dealloc'a koyabilirsiniz.
Phil

Yanıtlar:


112

Genel yanıt, "bildirimlere artık ihtiyacınız kalmadığında" olacaktır. Bu kesinlikle tatmin edici bir cevap değil.

Bir gözlemcinin kaydını temiz bir şekilde silmek için son şans olduğu için, gözlemci olarak kullanmayı düşündüğünüz bu sınıflara bir arama [notificationCenter removeObserver: self]yöntemi eklemenizi tavsiye ederim dealloc. Ancak bu, bildirim merkezinin ölü nesneleri bildirmesi nedeniyle sizi yalnızca çökmelere karşı koruyacaktır. Nesneleriniz henüz / artık bildirimi düzgün bir şekilde işleyebilecek durumda olmadığında kodunuzu bildirim almaya karşı koruyamaz. Bunun için ... Yukarıya bakınız.

Düzenleme (cevap düşündüğümden daha fazla yorum alıyor gibi göründüğü için) Burada söylemeye çalıştığım tek şey: gözlemciyi bildirim merkezinden ne zaman çıkarmanın en iyi olduğu konusunda genel bir tavsiye vermek gerçekten zor, çünkü bu şuna bağlıdır:

  • Kullanım durumunuz hakkında (Hangi bildirimler gözlenir? Ne zaman gönderilir?)
  • Gözlemcinin uygulaması (Ne zaman bildirim almaya hazır? Ne zaman artık hazır değil?)
  • Gözlemcinin amaçlanan yaşam süresi (Başka bir nesneye, örneğin bir görüntüye veya görüntü denetleyicisine bağlı mı?)
  • ...

Dolayısıyla, bulabileceğim en iyi genel tavsiye: uygulamanızı korumak için. En az bir olası başarısızlığa karşı, removeObserver:dansı yapın dealloc, çünkü bu (nesnenin yaşamında) bunu temiz bir şekilde yapabileceğiniz son nokta. Bunun anlamı şudur: "kaldırmayı deallocçağrılana kadar erteleyin ve her şey yoluna girecek". Bunun yerine, nesne artık bildirim almaya hazır (veya gerekli) olmadığında gözlemciyi kaldırın . Bu tam olarak doğru an. Ne yazık ki, yukarıda bahsedilen soruların hiçbirinin cevabını bilmediğimden, o anın ne zaman olacağını tahmin bile edemiyorum.

removeObserver:Bir nesneyi her zaman birden çok kez güvenli bir şekilde yapabilirsiniz (ve belirli bir gözlemciyle yapılan ilk çağrı hariç tümü nops olacaktır). Öyleyse: deallocemin olmak için bunu (tekrar) yapmayı düşünün , ancak her şeyden önce: uygun zamanda yapın (kullanım durumunuz tarafından belirlenir).


4
Bu, ARC ile güvenli değildir ve potansiyel olarak bir sızıntıya neden olabilir. Bu tartışmaya bakın: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon Bağlantı verdiğiniz makale benim amacıma ulaşmış gibi görünüyor. Neyi kaçırıyorum ?
Dirk

Sanırım gözlemcinin dealloc dışında başka bir yerden çıkarılması gerektiğine dikkat edilmelidir. Örneğin, viewwilldisappear
MobileMon

1
@MobileMon - evet. Umarım cevabımla karşılaştığım nokta budur. Gözlemciyi deallociçeride kaldırmak, ayrılmamış bir nesneye daha sonra erişim nedeniyle uygulamanın çökmesine karşı yalnızca son bir savunma hattıdır. Ancak bir gözlemcinin kaydını silmek için uygun yer genellikle başka bir yerdedir (ve genellikle nesnenin yaşam döngüsünde çok daha erken). Burada "Hey, sadece yap deallocve her şey yoluna girecek " demeye çalışmıyorum .
Dirk

@MobileMon "Örneğin, viewWillDisappear " Somut bir tavsiye sorun, bunun gerçekten ne tür bir olay için gözlemci olarak ne tür bir nesneyi kaydettiğinize bağlı olmasıdır. Olabilir bir gözlemciyi kaydını için doğru çözüm olabilir viewWillDisappear(ya viewDidUnloadiçin) UIViewControllers, ama bu gerçekten kullanım durumuna göre değişir.
Dirk

39

Not: Bu test edildi ve yüzde 100 çalışıyor

Swift

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.navigationController!.viewControllers.contains(self) == false  //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

PresentedViewController

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.isBeingDismissed()  //presented view controller
    {
        // remove observer here
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Amaç-C

Olarak iOS 6.0 > versiononun daha iyi, gözlemciyi kaldırmak viewWillDisappearolarak viewDidUnloadyöntem, kullanımdan kaldırıldı.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Defalarca onun daha iyi olduğunu remove observergörünüm kaldırıldı zaman navigation stack or hierarchy.

- (void)viewWillDisappear:(BOOL)animated{
 if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

PresentedViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

8
Onun bakış olduğunda bir kontrolör hâlâ bildirim isteyebilir dışında değil gösteren (örneğin bir tableView yeniden).
wcochran

2
@wcochran otomatik olarak yeniden doldur / yenileviewWillAppear:
Richard

@Prince, viewWillDisapper'ın neden Dealloc'dan daha iyi olduğunu açıklayabilir misiniz? bu yüzden kendimize gözlemci ekledik, bu yüzden benlik hafızadan düştüğünde, dealloc diyecek ve sonra tüm gözlemciler silinecek, bu iyi bir mantık değil.
Matrosov Alexander

removeObserver:selfHerhangi bir UIViewControlleryaşam döngüsü olayında arama yapmanın haftanızı mahvetmesi neredeyse garantidir. Daha fazla okuma: sübjektif
objektif

1
Kontrol cihazı aracılığıyla sunuluyorsa , removeObserveraramaları belirtildiği viewWillDisappeargibi yapmak kesinlikle doğru yoldur pushViewController. deallocBunun yerine onları yerleştirirseniz, o dealloczaman asla çağrılmayacak - en azından benim deneyimime göre ...
Christopher King

38

İOS 9'dan beri artık gözlemcileri kaldırmak gerekmiyor.

OS X 10.11 ve iOS 9.0'da NSNotificationCenter ve NSDistributedNotificationCenter artık ayrılabilecek kayıtlı gözlemcilere bildirim göndermeyecektir.

https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter


2
Belki gözlemcilere mesaj göndermeyecekler, ancak anladığım kadarıyla onlara güçlü bir gönderme yapacaklarına inanıyorum. Bu durumda tüm gözlemciler hafızada kalacak ve bir sızıntı üretecektir. Hatalıysam düzeltin.
köknar

6
Bağlantılı belgeler bununla ilgili ayrıntılara giriyor. TL; DR: zayıf bir referans.
Sebastian

ancak elbette, nesneyi etrafta referans olarak tutmanız ve artık bildirimleri dinlemek istememeniz durumunda hala gereklidir
TheEye

25

Gözlemci bir görünüm denetleyicisine eklenirse , onu eklemenizi viewWillAppearve çıkarmanızı şiddetle tavsiye ederim viewWillDisappear.


Merak ediyorum, @RickiG: viewControllers'ı viewWillAppearve viewWillDisappearviewControllers'ı kullanmayı neden öneriyorsunuz ?
Isaac Overacker

2
@IsaacOveracker birkaç nedenden ötürü: Kurulum kodunuz (örn. LoadView ve viewDidLoad) potansiyel olarak bildirimlerin tetiklenmesine neden olabilir ve denetleyicinizin bunu göstermeden önce yansıtması gerekir. Böyle yaparsanız birkaç faydası vardır. Şu anda denetleyiciden "ayrılmaya" karar verdiğinizde, bildirimleri önemsemiyorsunuz ve denetleyici ekrandan itilirken mantık yapmanıza neden olmayacaklar vb. ekran dışı sanırım bunu yapamazsınız. Ancak bunun gibi olaylar muhtemelen sizin modelinizde olmalıdır.
RickiG

1
@IsaacOveracker ayrıca ARC ile bildirimlere aboneliğinizi iptal etmek için dealloc uygulamak garip olurdu.
RickiG

4
UIViewControllers ile çalışırken gözlemcileri kaydetmenin / kaldırmanın en iyi yolu iOS7 ile denediklerim arasında. Tek yakalama, çoğu durumda UINavigationController'ı kullanırken ve başka bir UIViewController'ı yığına iterken gözlemcinin kaldırılmasını istememenizdir. Çözüm: [self isBeingDismissed] 'i çağırarak VC'nin viewWillDisappear'da açılıp açılmadığını kontrol edebilirsiniz.
lekksi

Görünüm denetleyicisini gezinme denetleyicisinden deallocaçmak, hemen çağrılmasına neden olmayabilir . Görünüm denetleyicisine geri dönmek, başlatma komutlarına gözlemci eklenirse birden çok bildirime neden olabilir.
Jonathan Lin

20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

4
Bu talimatların sırasını tersine çeviririm ... selfSonra kullanmak [super dealloc]beni sinirlendirir ... (alıcının işaretçiyi herhangi bir şekilde gerçekte reddetme olasılığı düşük olsa bile, nasıl uygulandığını asla bilemezsiniz NSNotificationCenter)
Dirk

Hm. Bu benim için çalıştı. Olağandışı bir davranış fark ettiniz mi?
Legolas

1
Dirk haklı - bu yanlış. [super dealloc]her zaman yönteminizin son ifadesi olmalıdır dealloc. Nesnenizi yok eder; çalıştırdıktan sonra selfartık geçerli bir hesabınız yok. / cc @Dirk
jscs

38
ARC'yi iOS 5+ üzerinde kullanıyorsanız [super dealloc], artık gerekli olmadığını düşünüyorum
pixelfreak

3
@pixelfreak güçlü, ARC altında [super dealloc] 'u aramasına izin verilmiyor
tapmonkey


7

Hızlı kullanımda dealloc kullanılamadığından deinit:

deinit {
    ...
}

Swift dokümantasyonu:

Bir sınıf örneğinin serbest bırakılmasından hemen önce bir deinitializer çağrılır. Deinitizers, intializer'ların init anahtar sözcüğü ile nasıl yazıldığına benzer şekilde deinit anahtar sözcüğüyle yazarsınız. Deinitializers yalnızca sınıf türlerinde kullanılabilir.

Örnekleriniz serbest bırakıldığında genellikle manuel temizlik yapmanız gerekmez. Ancak, kendi kaynaklarınızla çalışırken, kendi başınıza ek temizlik yapmanız gerekebilir. Örneğin, bir dosyayı açmak ve ona bazı veriler yazmak için özel bir sınıf oluşturursanız, sınıf örneğinin serbest bırakılmasından önce dosyayı kapatmanız gerekebilir.


5

* düzenleme: Bu tavsiye iOS <= 5 için geçerlidir (orada bile eklemeniz viewWillAppearve çıkarmanız gerekir viewWillDisappear- ancak herhangi bir nedenle gözlemciyi eklediyseniz tavsiye geçerlidir viewDidLoad)

Gözlemciyi eklediyseniz, viewDidLoadhem deallocve hem de viewDidUnload. Aksi takdirde viewDidLoad, arandığında iki kez eklemeniz gerekir viewDidUnload(bu bir hafıza uyarısından sonra gerçekleşir). Bu, viewDidUnloadkullanımdan kaldırıldığı ve çağrılmayacağı iOS 6'da gerekli değildir (çünkü görünümler artık otomatik olarak kaldırılmaz).


2
StackOverflow'a hoş geldiniz. Lütfen MarkDown SSS bölümüne bakın (soru / cevap düzenleme kutusunun yanındaki soru işareti simgesi). Markdwon'u kullanmak cevabınızın kullanılabilirliğini artıracaktır.
marko

5

Bence, şu kod hiçbir mantıklı ARC :

- (void)dealloc
{
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

Gelen iOS 6 , aynı zamanda gözlemci çıkarmadan hiçbir anlamı yoktur viewDidUnloadartık kullanımdan kaldırıldı çünkü.

Özetlemek gerekirse, bunu her zaman yapıyorum viewDidDisappear. Ancak, @Dirk'in dediği gibi gereksinimlerinize de bağlıdır.


Pek çok insan hala
iOS6'dan

ARC'de bu kodu [super dealloc] satırı olmadan kullanabilirsiniz; Burada daha fazlasını görebilirsiniz: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex

1
Ya normal bir NSObject bir bildirimin gözlemcisi olsaydı? Bu durumda dealloc kullanır mıydınız?
qix

4

Sanırım güvenilir bir cevap buldum ! Yukarıdaki cevaplar belirsiz ve çelişkili göründüğü için mecbur kaldım. Yemek Kitaplarını ve Programlama Kılavuzlarını inceledim.

İlk olarak, tarzı addObserver:içinde viewWillAppear:ve removeObserver:içindeviewWillDisappear: benim için çalışmalarını (test ettim) Ben ana görünümü denetleyicisi kod yürütmesine bir çocuk görünümü denetleyicisi bir bildirim gönderme kulüpler çünkü yok. Bu stili yalnızca bildirimi aynı görünüm denetleyicisinde yayınlıyor ve dinliyorsam kullanırım.

En çok güveneceğim cevabı iOS Programlama: Big Nerd Ranch Guide 4th'de buldum. BNR çalışanlarına güveniyorum çünkü iOS eğitim merkezleri var ve sadece başka bir yemek kitabı yazmıyorlar. Doğru olması muhtemelen onların çıkarına olacaktır.

BNR örneği bir: addObserver:içinde init:,removeObserver: içindedealloc:

BNR örneği iki: addObserver:in awakeFromNib:,removeObserver: içindedealloc:

… Gözlemciyi kaldırırken dealloc: onlar kullanımını yok[super dealloc];

Umarım bu bir sonraki kişiye yardımcı olur ...

Bu gönderiyi güncelliyorum çünkü Apple artık Storyboard'larla neredeyse tamamen gitti, bu yüzden yukarıda belirtilenler tüm durumlar için geçerli olmayabilir. Önemli olan (ve ilk etapta bu yazıyı eklememin nedeni), aranıp aranmadığına dikkat etmektir viewWillDisappear:. Uygulama arka plana girdiğinde benim için değildi.


Bağlam önemli olduğu için bunun doğru olup olmadığını söylemek zor. Zaten birkaç kez bahsedilmiştir, ancak dealloc bir ARC bağlamında çok az anlam ifade etmektedir (şimdiye kadarki tek bağlam budur). Dealloc çağrıldığında da tahmin edilemez - viewWillDisappear'ın kontrol edilmesi daha kolaydır. Bir yan not: Çocuğunuzun ebeveynine bir şey iletmesi gerekiyorsa, temsilci modeli kulağa daha iyi bir seçim gibi geliyor.
RickiG

2

Kabul edilen yanıt güvenli değil ve bellek sızıntısına neden olabilir. Lütfen kayıt silme işlemini dealloc'ta bırakın, aynı zamanda görünümde kaydı silin WillDisappear (tabii ki, eğer viewWillAppear'a kaydolursanız) .... BU HER ŞEY YAPTIM VE BÜYÜK ÇALIŞIYOR! :)


1
Bu cevaba katılıyorum. ViewWillDisappear'daki gözlemcileri kaldırmazsam, uygulamanın yoğun kullanımından sonra çökmelere yol açan bellek uyarıları ve sızıntıları yaşıyorum.
SarpErdag

2

Ayrıca şunu da fark etmek önemlidir: viewWillDisappearGörünüm denetleyicisi yeni bir UIView da çağrıldığına . Bu delege, yalnızca görünüm denetleyicisi ana görünümünün ekranda görünmediğini belirtir.

Bu durumda, bildirimin serbest bırakılması viewWillDisappear UI görünümünün üst görünüm denetleyicisiyle iletişim kurmasına izin vermek için bildirimi kullanıyorsak, bildirimin serbest bırakılması uygun olmayabilir.

Çözüm olarak genellikle gözlemciyi şu iki yöntemden biriyle kaldırırım:

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"viewController will disappear");
    if ([self isBeingDismissed]) {
        NSLog(@"viewController is being dismissed");
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
    }
}

-(void)dealloc {
    NSLog(@"viewController is being deallocated");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}

Benzer nedenlerle, bildirimi ilk kez yayınladığımda, denetleyicinin üzerinde görünen bir görünümün her seferinde viewWillAppearyöntemin çalıştırıldığı gerçeğini hesaba katmam gerekir. Bu da aynı bildirimin birden çok kopyasını oluşturacaktır. Bir bildirimin zaten etkin olup olmadığını kontrol etmenin bir yolu olmadığından, eklemeden önce bildirimi kaldırarak sorunu ortadan kaldırıyorum:

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"viewController will appear");
    // Add observers
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"imageGenerated" object:nil]; // This is added to avoid duplicate notifications when the view is presented again
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedImageFromCameraOrPhotolibraryMethodOnListener:) name:@"actionCompleted" object:nil];

}

-1

SWIFT 3

Bildirimleri kullanmanın iki durumu vardır: - bunlara yalnızca görünüm denetleyicisi ekrandayken ihtiyaç duyulur; - Kullanıcı mevcut olanın üzerinde başka bir ekran açsa bile her zaman gereklidir.

İlk durumda, gözlemci eklemek ve çıkarmak için doğru yer:

/// Add observers
///
/// - Parameter animated: the animation flag
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

ikinci durum için doğru yol şudur:

/// Add observers
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isBeingDismissed // remove only when view controller is removed disappear forever
    || !(self.navigationController?.viewControllers.contains(self) ?? true) {
        NotificationCenter.default.removeObserver(self)
    }
}

Ve koymak asla removeObserveriçinde deinit{ ... }- bu bir hata!


-1
override func viewDidLoad() {   //add observer
  super.viewDidLoad()
  NotificationCenter.default.addObserver(self, selector:#selector(Yourclassname.method), name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}

override func viewWillDisappear(_ animated: Bool) {    //remove observer
    super.viewWillDisappear(true)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}
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.