Bagaimana saya bisa tahu jika suatu objek memiliki pengamat nilai kunci terlampir


142

jika Anda memberi tahu objek c objektif untuk removeObservers: untuk lintasan kunci dan lintasan kunci itu belum terdaftar, itu akan memecahkan sads. Suka -

'Tidak dapat menghapus pengamat untuk jalur kunci "theKeyPath" dari karena tidak terdaftar sebagai pengamat.'

apakah ada cara untuk menentukan apakah suatu objek memiliki pengamat terdaftar, jadi saya bisa melakukan ini

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

Saya masuk ke skenario ini memperbarui aplikasi lama di iOS 8 di mana view controller sedang tidak dialokasikan dan melempar pengecualian "Tidak dapat menghapus". Saya berpikir bahwa dengan memanggil addObserver:dalam viewWillAppear:dan Sejalan removeObserver:di viewWillDisappear:, panggilan yang benar dipasangkan. Saya harus membuat perbaikan cepat jadi saya akan mengimplementasikan solusi try-catch dan meninggalkan komentar untuk menyelidiki penyebabnya lebih lanjut.
bneely

Saya hanya berurusan dengan sesuatu yang serupa dan saya melihat saya perlu melihat ke dalam desain saya lebih dalam dan menyesuaikannya sehingga saya tidak perlu menghapus pengamat lagi.
Bogdan

menggunakan nilai bool seperti yang disarankan dalam jawaban ini bekerja paling baik untuk saya: stackoverflow.com/a/37641685/4833705
Lance Samaria

Jawaban:


315

Cobalah menangkap panggilan removeObserver Anda

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

12
1+ Jawaban bagus, berhasil untuk saya dan saya setuju dengan kata-kata kasar Anda sebelum diedit.
Robert

25
Dipilih untuk kata-kata kasar yang dihapus yang kemungkinan besar akan saya setujui.
Ben Gotow

12
Bukankah di sini ada solusi elegan lainnya? yang ini membutuhkan setidaknya 2ms per penggunaan ... bayangkan di tableviewcell
João Nunes

19
Diturunkan karena Anda mengabaikan untuk mengatakan bahwa ini tidak aman untuk kode produksi dan cenderung gagal kapan saja. Memunculkan pengecualian melalui kode kerangka kerja bukanlah opsi di Kakao.
Nikolai Ruhe

6
Cara menggunakan kode ini di swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} tangkap error let sebagai NSError {print (error.localizedDescription)} mendapatkan peringatan.
Vipulk617

37

Pertanyaan sebenarnya adalah mengapa Anda tidak tahu apakah Anda mengamatinya atau tidak.

Jika Anda melakukan ini di kelas objek yang diamati, berhenti. Apa pun yang mengamatinya berharap untuk terus mengamatinya. Jika Anda memotong notifikasi pengamat tanpa sepengetahuannya, mengharapkan hal-hal akan rusak; lebih khusus, berharap kondisi pengamat basi karena tidak menerima pembaruan dari objek yang sebelumnya diamati.

Jika Anda melakukan ini di kelas objek pengamatan, cukup ingat objek mana yang Anda amati (atau, jika Anda hanya mengamati satu objek, apakah Anda mengamatinya). Ini mengasumsikan bahwa pengamatan itu dinamis dan antara dua objek yang tidak berhubungan; jika pengamat memiliki yang diamati, cukup tambahkan pengamat setelah Anda membuat atau menyimpan yang diamati, dan lepaskan pengamat sebelum Anda melepaskan yang diamati.

Menambah dan menghapus objek sebagai pengamat biasanya terjadi di kelas pengamat, dan tidak pernah di objek yang diamati.


14
Use Case: Anda ingin menghapus pengamat di viewDidUnload, dan juga di dealloc. Ini menghapus mereka dua kali dan akan membuang pengecualian jika viewController Anda diturunkan dari peringatan memori, dan kemudian juga dirilis. Bagaimana Anda menyarankan penanganan skenario ini?
bandejapaisa

2
@bandejapaisa: Cukup banyak apa yang saya katakan dalam jawaban saya: Lacak apakah saya mengamati dan hanya mencoba berhenti mengamati jika saya melakukannya.
Peter Hosey

41
Tidak, itu bukan pertanyaan yang menarik. Anda tidak harus melacak hal ini; Anda harus dapat dengan mudah membatalkan registrasi semua pendengar di dealloc, tanpa peduli apakah Anda menabrak jalur kode di mana ia ditambahkan atau tidak. Ini harus bekerja seperti removeObserver NSNotificationCenter, yang tidak peduli jika Anda benar-benar memilikinya atau tidak. Pengecualian ini hanya membuat bug di mana tidak ada yang dinyatakan ada, yang merupakan desain API buruk.
Glenn Maynard

1
@GlennMaynard: Seperti yang saya katakan dalam jawaban, “Jika Anda memotong notifikasi pengamat tanpa sepengetahuannya, perkirakan semuanya akan rusak; lebih khusus, berharap kondisi pengamat basi karena tidak menerima pembaruan dari objek yang sebelumnya diamati. " Setiap pengamat harus mengakhiri pengamatannya sendiri; kegagalan untuk melakukan ini idealnya sangat terlihat.
Peter Hosey

3
Tidak ada dalam pertanyaan yang berbicara tentang menghapus pengamat kode lain .
Glenn Maynard

25

FWIW, [someObject observationInfo]tampaknya niljika someObjecttidak memiliki pengamat. Saya tidak akan mempercayai perilaku ini, karena saya belum melihatnya didokumentasikan. Juga, saya tidak tahu cara membaca observationInfountuk mendapatkan pengamat tertentu.


Apakah Anda tahu bagaimana saya dapat mengambil pengamat tertentu? objectAtIndex:tidak menghasilkan hasil yang diinginkan.)
Eimantas

1
@MattDiPasquale Apakah Anda tahu bagaimana saya bisa membaca kode observasi? Dalam cetakan itu keluar baik-baik saja, tetapi itu adalah pointer untuk membatalkan. Bagaimana saya harus membacanya?
neeraj

observasiInfo adalah metode debugging yang didokumentasikan dalam kertas debug Xcode (sesuatu dengan "sihir" dalam judul). Anda dapat mencoba mencarinya. Saya dapat mengatakan bahwa jika Anda perlu tahu apakah seseorang mengamati objek Anda - Anda melakukan sesuatu yang salah. Pikirkan kembali arsitektur dan logika Anda. Belajar dengan cara yang sulit.)
Eimantas

Sumber:NSKeyValueObserving.h
nefarianblack

ditambah 1 untuk jalan buntu yang lucu tapi masih agak membantu
Will Von Ullrich

4

Satu-satunya cara untuk melakukan ini adalah dengan menetapkan bendera ketika Anda menambahkan pengamat.


3
Jika Anda berakhir dengan BOL di mana-mana, lebih baik lagi membuat objek pembungkus KVO yang menangani menambahkan pengamat dan menghapusnya. Ini dapat memastikan pengamat Anda hanya dihapus sekali. Kami telah menggunakan objek seperti ini, dan itu berfungsi.
bandejapaisa

ide bagus jika Anda tidak selalu mengamati.
Andre Simon

4

Saat Anda menambahkan pengamat ke objek Anda bisa menambahkannya ke NSMutableArrayseperti ini:

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

Jika Anda ingin menghapus objek, Anda dapat melakukan sesuatu seperti:

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

Ingat, jika Anda tidak melihat satu objek menghapusnya dari _observedObjectsarray:

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

1
Jika ini terjadi di dunia multi-utas, Anda perlu memastikan array Anda adalah ThreadSafe
shrutim

Anda menyimpan referensi yang kuat dari suatu objek, yang akan meningkatkan jumlah penyimpanan setiap kali sebuah objek ditambahkan dalam daftar dan tidak akan dibatalkan alokasi kecuali jika referensi dihapus dari array. Saya lebih suka menggunakan NSHashTable/ NSMapTableuntuk menyimpan referensi yang lemah.
atulkhatri

3

Menurut pendapat saya - ini bekerja mirip dengan mekanisme retainCount. Anda tidak dapat memastikan bahwa saat ini Anda memiliki pengamat. Bahkan jika Anda memeriksa: self.observationInfo - Anda tidak dapat mengetahui dengan pasti bahwa Anda akan memiliki / tidak akan memiliki pengamat di masa mendatang.

Seperti retainCount . Mungkin metode observasiInfo bukan jenis yang tidak berguna, tapi saya hanya menggunakannya untuk keperluan debug.

Jadi sebagai hasilnya - Anda hanya perlu melakukannya seperti dalam manajemen memori. Jika Anda menambahkan pengamat - hapus saja ketika Anda tidak membutuhkannya. Seperti menggunakan metode viewWillAppear / viewWillDisappear dll. Misalnya:

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

Dan Anda membutuhkan beberapa pemeriksaan khusus - mengimplementasikan kelas Anda sendiri yang menangani berbagai pengamat dan menggunakannya untuk pemeriksaan Anda.


[self removeObserver:nil forKeyPath:@""]; perlu pergi sebelum: [super viewWillDisappear:animated];
Joshua Hart

@ JoshuaHart mengapa?
quarezz

Karena ini adalah metode penghancuran (dealloc). Ketika Anda mengganti beberapa jenis metode teardown, Anda menelepon super terakhir. Seperti: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

viewWillDisapear bukan metode meruntuhkan dan tidak memiliki koneksi dengan dealloc. Jika Anda mendorong maju ke tumpukan navigasi, viewWillDisapear akan dipanggil, tetapi tampilan Anda akan tetap dalam memori. Saya melihat di mana Anda akan pergi dengan logika setup / teardown, tetapi melakukannya di sini tidak akan memberikan manfaat yang sebenarnya. Anda ingin menempatkan penghapusan sebelum super hanya jika Anda memiliki beberapa logika di kelas dasar, yang dapat bertentangan dengan pengamat saat ini.
quarezz

3

[someObject observationInfo]kembali niljika tidak ada pengamat.

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

}

Sesuai dengan dokumen Apple: observInfo mengembalikan pointer yang mengidentifikasi informasi tentang semua pengamat yang terdaftar dengan penerima.
FredericK

Ini lebih baik dikatakan dalam jawaban @ mattdipasquale
Ben Leggiero

2

Inti dari pola pengamat adalah untuk memungkinkan kelas yang diamati "disegel" - untuk tidak tahu atau peduli apakah itu sedang diamati. Anda secara eksplisit mencoba untuk mematahkan pola ini.

Mengapa?

Masalah yang Anda hadapi adalah Anda berasumsi sedang diamati padahal sebenarnya tidak. Objek ini tidak memulai pengamatan. Jika Anda ingin kelas Anda memiliki kendali atas proses ini, maka Anda harus mempertimbangkan untuk menggunakan pusat notifikasi. Dengan begitu kelas Anda memiliki kontrol penuh kapan data dapat diamati. Karena itu, tidak peduli siapa yang menonton.


10
Dia bertanya bagaimana pendengar dapat mengetahui apakah itu mendengarkan sesuatu, bukan bagaimana objek yang diamati dapat mengetahui apakah itu sedang diamati.
Glenn Maynard

1

Saya bukan penggemar yang mencoba menangkap solusi jadi apa yang saya lakukan sebagian besar waktu adalah bahwa saya membuat metode berlangganan dan berhenti berlangganan untuk pemberitahuan khusus di dalam kelas itu. Misalnya dua metode ini berlangganan atau berhenti berlangganan objek ke notifikasi keyboard global:

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

Di dalam metode itu saya menggunakan properti pribadi yang disetel ke benar atau salah, bergantung pada status berlangganan seperti:

@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

Selain jawaban Adam saya ingin menyarankan untuk menggunakan makro seperti ini

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

contoh penggunaan

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

1
Seberapa gila itu karena melemparkan pengecualian? Mengapa tidak melakukan apa-apa jika tidak ada yang menempel?
Aran Mulholland
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.