Objective-C: Di mana menghapus pengamat untuk NSNotification?

102

Saya memiliki kelas C obyektif. Di dalamnya, saya membuat metode init dan menyiapkan NSNotification di dalamnya

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

Di mana saya mengatur [[NSNotificationCenter defaultCenter] removeObserver:self]di kelas ini? Saya tahu bahwa untuk a UIViewController, saya bisa menambahkannya ke dalam viewDidUnloadmetode Jadi apa yang perlu dilakukan jika saya baru saja membuat Kelas c tujuan?

Zhen
sumber
Saya memasukkannya ke dalam metode dealloc.
onnoweb
1
Metode dealloc tidak dibuat secara otomatis untuk saya ketika saya membuat kelas tujuan c, jadi bolehkah saya menambahkannya?
Zhen
Ya, Anda dapat menerapkan -(void)deallocdan kemudian menambahkannya removeObserser:self. Ini adalah cara yang paling direkomendasikan untuk melakukannyaremoveObservers:self
petershine
Apakah masih boleh menerapkan deallocmetode ini di iOS 6?
wcochran
2
Ya, tidak masalah menggunakan dealloc dalam proyek ARC selama Anda tidak memanggil [super dealloc] (Anda akan mendapatkan kesalahan kompiler jika memanggil [super dealloc]). Dan ya, Anda pasti bisa meletakkan removeObserver di dealloc.
Phil

Jawaban:

112

Jawaban umumnya adalah "segera setelah Anda tidak lagi membutuhkan notifikasi". Ini jelas bukan jawaban yang memuaskan.

Saya akan merekomendasikan, bahwa Anda menambahkan panggilan [notificationCenter removeObserver: self]dalam metode deallockelas-kelas itu, yang ingin Anda gunakan sebagai pengamat, karena ini adalah kesempatan terakhir untuk membatalkan pendaftaran pengamat dengan rapi. Namun, ini hanya akan melindungi Anda dari crash karena pusat notifikasi memberi tahu benda mati. Itu tidak dapat melindungi kode Anda dari menerima pemberitahuan, ketika objek Anda belum / tidak lagi dalam keadaan di mana mereka dapat menangani pemberitahuan dengan benar. Untuk ini ... Lihat di atas.

Sunting (karena jawabannya tampaknya menarik lebih banyak komentar daripada yang saya kira) Yang ingin saya katakan di sini adalah: sangat sulit untuk memberikan nasihat umum tentang kapan waktu terbaik untuk menghapus pengamat dari pusat pemberitahuan, karena itu tergantung:

  • Mengenai kasus penggunaan Anda (Pemberitahuan mana yang diamati? Kapan mereka dikirim?)
  • Implementasi pengamat (Kapan siap menerima notifikasi? Kapan tidak lagi siap?)
  • Waktu hidup yang diinginkan dari pengamat (Apakah terikat pada objek lain, katakanlah, view atau view controller?)
  • ...

Jadi, saran umum terbaik yang bisa saya berikan: untuk melindungi aplikasi Anda. terhadap setidaknya satu kemungkinan kegagalan, lakukan removeObserver:tariannya dealloc, karena itulah titik terakhir (dalam kehidupan objek), di mana Anda dapat melakukannya dengan rapi. Ini tidak berarti: "cukup tunda penghapusan sampai deallocdipanggil, dan semuanya akan baik-baik saja". Sebagai gantinya, hapus pengamat segera setelah objek tidak lagi siap (atau diperlukan) untuk menerima notifikasi . Itu adalah momen yang tepat. Sayangnya, karena tidak mengetahui jawaban atas semua pertanyaan yang disebutkan di atas, saya bahkan tidak dapat menebak, kapan saat itu akan tiba.

Anda selalu dapat dengan aman removeObserver:suatu objek beberapa kali (dan semua kecuali panggilan pertama dengan pengamat tertentu akan menjadi nops). Jadi: pikirkan untuk melakukannya (lagi) deallochanya untuk memastikan, tetapi yang pertama dan terpenting: lakukan pada saat yang tepat (yang ditentukan oleh kasus penggunaan Anda).

Beladau
sumber
4
Ini tidak aman dengan ARC dan berpotensi menyebabkan kebocoran. Lihat diskusi ini: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon Artikel yang Anda tautkan tampaknya menjelaskan maksud saya. Apa yang saya lewatkan?
Dirk
Saya kira itu harus dicatat bahwa seseorang harus menghapus pengamat di tempat lain selain dealloc. Misalnya, tampilan akan hilang
MobileM pada
1
@MobileMon - ya. Saya berharap, itulah poin yang saya sampaikan dengan jawaban saya. Menghapus pengamat di deallochanyalah garis pertahanan terakhir agar aplikasi tidak mogok karena nantinya akses ke objek yang telah didekalokasi. Tetapi tempat yang tepat untuk membatalkan pendaftaran pengamat biasanya di tempat lain (dan seringkali, jauh lebih awal dalam siklus hidup objek). Saya tidak mencoba untuk mengatakan di sini "Hei, lakukan saja deallocdan semuanya akan baik-baik saja".
Dirk
@MobileMon "Misalnya, viewWillDisappear" Masalah dengan memberikan saran konkret adalah, hal itu sangat bergantung pada jenis objek yang Anda daftarkan sebagai pengamat untuk jenis acara apa. Ini mungkin solusi yang tepat untuk membatalkan pendaftaran pengamat di viewWillDisappear(atau viewDidUnload) untuk UIViewControllers, tetapi itu benar-benar tergantung pada kasus penggunaan.
Dirk
39

Catatan: Ini telah diuji dan berfungsi 100% persen

Cepat

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

Objective-C

Dalam hal iOS 6.0 > version, lebih baik untuk menghapus pengamat viewWillDisappearkarena viewDidUnloadmetode tidak digunakan lagi.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Ada banyak kali lebih baik remove observerbila tampilan telah dihapus dari 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];
    }
}
Paresh Navadiya
sumber
8
Kecuali pengontrol mungkin masih menginginkan notifikasi saat tampilannya tidak muncul (misalnya untuk memuat ulang tableView).
wcochran
2
@wcochran secara otomatis memuat / menyegarkanviewWillAppear:
Richard
@Prince dapatkah Anda menjelaskan mengapa viewWillDisapper lebih baik daripada dealloc? jadi kita telah menambahkan pengamat ke diri sendiri, jadi ketika diri akan dihapus dari memori akan memanggil dealloc dan kemudian semua pengamat akan dihapus, bukankah ini logika yang baik.
Matrosov Alexander
Menelepon removeObserver:selfdi salah satu UIViewControlleracara siklus hidup hampir pasti akan merusak minggu Anda. Bacaan lebih lanjut: subyektif-objek-c.blogspot.com/2011/04/…
cbowns
1
Menempatkan removeObserverpanggilan viewWillDisappearseperti yang ditunjukkan jelas merupakan cara yang tepat untuk pergi jika pengontrol disajikan melalui pushViewController. Jika Anda memasukkannya, deallocmaka dealloctidak akan pernah dipanggil - setidaknya menurut pengalaman saya ...
Christopher King
38

Sejak iOS 9, tidak perlu lagi menghapus pengamat.

Di OS X 10.11 dan iOS 9.0 NSNotificationCenter dan NSDistributedNotificationCenter tidak akan lagi mengirim pemberitahuan ke pengamat terdaftar yang mungkin dibatalkan alokasinya.

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

Sebastian
sumber
2
Mungkin mereka tidak akan mengirim pesan ke pengamat, tapi saya yakin mereka akan menyimpan referensi yang kuat kepada mereka seperti yang saya mengerti. Dalam hal ini semua pengamat akan tetap berada dalam memori dan menghasilkan kebocoran. Koreksi saya jika saya salah.
cemara
6
Dokumentasi terkait menjelaskan secara detail tentang itu. TL; DR: ini referensi yang lemah.
Sebastian
tetapi tentu saja itu masih diperlukan jika Anda menyimpan objek yang mereferensikannya dan hanya tidak ingin mendengarkan pemberitahuan lagi
TheEye
25

Jika pengamat ditambahkan ke pengontrol tampilan , saya sangat menyarankan untuk menambahkan viewWillAppeardan menghapusnya viewWillDisappear.

RickiG
sumber
Saya penasaran, @RickiG: mengapa Anda merekomendasikan penggunaan viewWillAppeardan viewWillDisappearuntuk viewControllers?
Isaac Overacker
2
@IsaacOveracker beberapa alasan: kode penyiapan Anda (mis. LoadView dan viewDidLoad) berpotensi menyebabkan pemberitahuan diaktifkan dan pengontrol Anda perlu merefleksikannya sebelum ditampilkan. Jika Anda melakukannya seperti ini, ada beberapa keuntungan. Pada saat Anda memutuskan untuk "meninggalkan" pengontrol Anda tidak peduli dengan pemberitahuan dan mereka tidak akan menyebabkan Anda melakukan logika saat pengontrol sedang didorong dari layar dll. Ada kasus khusus di mana pengontrol harus menerima pemberitahuan saat itu off-screen Saya kira Anda tidak bisa melakukan ini. Tetapi peristiwa seperti itu mungkin harus ada dalam model Anda.
RickiG
1
@IsaacOveracker juga dengan ARC akan aneh menerapkan dealloc untuk berhenti berlangganan pemberitahuan.
RickiG
4
Dari semua yang saya coba, dengan iOS7 ini adalah cara terbaik untuk mendaftarkan / menghapus pengamat saat bekerja dengan UIViewControllers. Satu-satunya tangkapan adalah, dalam banyak kasus Anda tidak ingin pengamat dihapus saat menggunakan UINavigationController dan mendorong UIViewController lain ke tumpukan. Solusi: Anda dapat memeriksa apakah VC dimunculkan di viewWillDisappear dengan memanggil [self isBeingDismissed].
lekksi
Memunculkan pengontrol tampilan dari pengontrol navigasi mungkin tidak menyebabkan deallocpanggilan segera. Kembali ke pengontrol tampilan kemudian dapat menyebabkan beberapa pemberitahuan jika pengamat ditambahkan dalam perintah inisialisasi.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
sumber
4
Saya akan mengubah urutan instruksi ini sekitar ... Menggunakan selfsetelah [super dealloc]membuat saya gugup ... (bahkan jika penerima tidak mungkin benar-benar membedakan penunjuk dengan cara apa pun, Anda tidak pernah tahu, bagaimana penerapannya NSNotificationCenter)
Dirk
Hm. ini berhasil untuk ku. Pernahkah Anda memperhatikan perilaku yang tidak biasa?
Legolas
1
Dirk benar - ini tidak benar. [super dealloc]harus selalu menjadi pernyataan terakhir dari deallocmetode Anda . Ini menghancurkan objek Anda; setelah dijalankan, Anda tidak memiliki valid selflagi. / cc @Dirk
jscs
38
Jika menggunakan ARC di iOS 5+, saya rasa [super dealloc]tidak diperlukan lagi
pixelfreak
3
@pixelfreak lebih kuat, tidak diizinkan di bawah ARC untuk memanggil [super dealloc]
tapmonkey
8

Secara umum saya memasukkannya ke dalam deallocmetode.

Raphael Petegrosso
sumber
7

Dalam penggunaan cepat deinit karena dealloc tidak tersedia:

deinit {
    ...
}

Dokumentasi cepat:

Deinitializer dipanggil segera sebelum instance kelas dibatalkan alokasinya. Anda menulis deinitializers dengan kata kunci deinit, mirip dengan bagaimana inisialalizer ditulis dengan kata kunci init. Deinitializers hanya tersedia pada tipe kelas.

Biasanya Anda tidak perlu melakukan pembersihan manual saat instans Anda dibatalkan alokasinya. Namun, saat Anda bekerja dengan sumber daya Anda sendiri, Anda mungkin perlu melakukan beberapa pembersihan tambahan sendiri. Misalnya, jika Anda membuat kelas kustom untuk membuka file dan menulis beberapa data ke dalamnya, Anda mungkin perlu menutup file sebelum instance kelas dibatalkan alokasinya.

Morten Holmgaard
sumber
5

* edit: Saran ini berlaku untuk iOS <= 5 (bahkan di sana Anda harus menambahkan viewWillAppeardan menghapus viewWillDisappear- namun saran tersebut berlaku jika karena alasan tertentu Anda telah menambahkan pengamat viewDidLoad)

Jika Anda telah menambahkan pengamat di viewDidLoadAnda harus menghapusnya di kedua deallocdan viewDidUnload. Jika tidak, Anda akan menambahkannya dua kali saat viewDidLoaddipanggil setelahnya viewDidUnload(ini akan terjadi setelah peringatan memori). Ini tidak diperlukan di iOS 6 yang viewDidUnloadsudah tidak digunakan lagi dan tidak akan dipanggil (karena tampilan tidak lagi otomatis diturunkan).

Ehren
sumber
2
Selamat datang di StackOverflow. Silakan periksa FAQ Markdown (ikon tanda tanya di sebelah kotak edit pertanyaan / jawaban). Menggunakan Markdwon akan meningkatkan kegunaan jawaban Anda.
marko
5

Menurut pendapat saya, kode berikut tidak masuk akal di ARC :

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

Di iOS 6 , juga tidak ada gunanya menghapus pengamat viewDidUnload, karena sekarang sudah tidak digunakan lagi.

Singkatnya, saya selalu melakukannya viewDidDisappear. Namun, itu juga tergantung pada kebutuhan Anda, seperti yang dikatakan @Dirk.

kimimaro
sumber
Banyak orang masih menulis kode untuk versi iOS yang lebih lama daripada iOS6 .... :-)
lnafziger
Di ARC Anda dapat menggunakan kode ini tetapi tanpa baris [super dealloc]; Anda dapat melihat lebih banyak di sini: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
Bagaimana jika Anda memiliki NSObject biasa menjadi pengamat notifikasi? Apakah Anda akan menggunakan dealloc dalam kasus ini?
qix
4

Saya rasa saya menemukan jawaban yang dapat diandalkan ! Saya harus melakukannya, karena jawaban di atas ambigu dan tampak bertentangan. Saya melihat-lihat Buku Masakan dan Panduan Pemrograman.

Pertama, gaya addObserver:dalam viewWillAppear:dan removeObserver:dalam viewWillDisappear:tidak berfungsi untuk saya (saya mengujinya) karena saya memposting pemberitahuan di pengontrol tampilan anak untuk mengeksekusi kode di pengontrol tampilan induk. Saya hanya akan menggunakan gaya ini jika saya memposting dan mendengarkan notifikasi dalam pengontrol tampilan yang sama.

Jawaban yang paling saya andalkan, saya temukan di Pemrograman iOS: Big Nerd Ranch Guide 4th. Saya mempercayai orang-orang BNR karena mereka memiliki pusat pelatihan iOS dan mereka tidak hanya menulis buku masak lain. Mungkin demi kepentingan terbaik mereka agar akurat.

BNR contoh satu: addObserver:in init:, removeObserver:indealloc:

BNR contoh dua: addObserver:in awakeFromNib:, removeObserver:indealloc:

… Saat menghapus pengamat di dalamnya dealloc:tidak digunakan[super dealloc];

Saya harap ini membantu orang berikutnya ...

Saya memperbarui posting ini karena Apple sekarang hampir sepenuhnya hilang dengan Storyboards sehingga hal yang disebutkan di atas mungkin tidak berlaku untuk semua situasi. Yang penting (dan alasan saya menambahkan posting ini di tempat pertama) adalah memperhatikan jika Anda viewWillDisappear:dipanggil. Bukan untuk saya ketika aplikasi memasuki latar belakang.

Murat Zazi
sumber
Sulit untuk mengatakan apakah ini benar karena konteksnya penting. Ini sudah disebutkan beberapa kali, tetapi dealloc tidak masuk akal dalam konteks ARC (yang merupakan satu-satunya konteks saat ini). Ini juga tidak dapat diprediksi ketika dealloc dipanggil - viewWillDisappear lebih mudah dikendalikan. Catatan tambahan: Jika anak Anda perlu mengkomunikasikan sesuatu kepada orang tuanya, pola delegasi terdengar seperti pilihan yang lebih baik.
RickiG
2

Jawaban yang diterima tidak aman dan dapat menyebabkan kebocoran memori. Harap jangan tinggalkan unregister di dealloc tetapi juga batalkan pendaftaran di viewWillDisappear (tentu saja jika Anda mendaftar di viewWillAppear) .... ITU APA YANG SAYA LAKUKAN TAPI DAN BEKERJA HEBAT! :)

MobileMon
sumber
1
Saya setuju dengan jawaban ini. Saya mengalami peringatan dan kebocoran memori yang menyebabkan mogok setelah penggunaan aplikasi secara intensif jika saya tidak menghapus pengamat di viewWillDisappear.
SarpErdag
2

Penting untuk diperhatikan juga bahwa viewWillDisappearyang dipanggil juga saat pengontrol tampilan menyajikan UIView baru. Delegasi ini hanya menunjukkan bahwa tampilan utama pengontrol tampilan tidak terlihat di layar.

Dalam kasus ini, viewWillDisappearmembatalkan alokasi notifikasi mungkin merepotkan jika kami menggunakan notifikasi untuk memungkinkan UIview berkomunikasi dengan pengontrol tampilan induk.

Sebagai solusi, saya biasanya menghapus pengamat dalam salah satu dari dua metode ini:

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

Untuk alasan serupa, ketika saya mengeluarkan notifikasi pertama kali, saya perlu memperhitungkan fakta bahwa setiap kali tampilan dengan muncul di atas pengontrol maka viewWillAppearmetode dipecat. Ini pada gilirannya akan menghasilkan banyak salinan dari pemberitahuan yang sama. Karena tidak ada cara untuk memeriksa apakah pemberitahuan sudah aktif, saya mengatasi masalah dengan menghapus pemberitahuan sebelum menambahkannya:

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

}
Alex
sumber
-1

SWIFT 3

Ada dua kasus penggunaan notifikasi: - notifikasi diperlukan hanya saat pengontrol tampilan ada di layar; - mereka selalu dibutuhkan, bahkan jika pengguna membuka layar lain melalui arus.

Untuk kasus pertama tempat yang tepat untuk menambah dan menghapus pengamat adalah:

/// 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)
}

untuk kasus kedua cara yang benar adalah:

/// 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)
    }
}

Dan tidak pernah menaruh removeObserverdi deinit{ ... }- itu KESALAHAN sebuah!

Alexander Volkov
sumber
-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)
}
urvashi bhagat
sumber