ฉันจะบอกได้อย่างไรว่าวัตถุนั้นมีผู้สังเกตการณ์ค่าติดอยู่


142

ถ้าคุณบอกวัตถุ c วัตถุประสงค์เพื่อ removeObservers: สำหรับเส้นทางที่สำคัญและไม่ได้ลงทะเบียนเส้นทางที่สำคัญนั้นมันแตกร้าว sads ชอบ -

'ไม่สามารถลบผู้สังเกตการณ์สำหรับเส้นทางหลัก "theKeyPath" เนื่องจากไม่ได้ลงทะเบียนเป็นผู้สังเกตการณ์'

มีวิธีการตรวจสอบว่าวัตถุมีผู้สังเกตการณ์ที่ลงทะเบียนหรือไม่ดังนั้นฉันสามารถทำได้

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

ฉันเข้าสู่สถานการณ์นี้เพื่ออัปเดตแอปเก่าบน iOS 8 ซึ่งมีการยกเลิกการจัดสรรตัวควบคุมมุมมองและโยนข้อยกเว้น "ไม่สามารถลบได้" ผมคิดว่าโดยการโทรaddObserver:ในviewWillAppear:และตามลําดับremoveObserver:ในviewWillDisappear:การโทรที่ถูกจับคู่ได้อย่างถูกต้อง ฉันต้องทำการแก้ไขอย่างรวดเร็วดังนั้นฉันจะใช้โซลูชันลองจับและแสดงความคิดเห็นเพื่อตรวจสอบสาเหตุเพิ่มเติม
bneely

ฉันแค่จัดการกับสิ่งที่คล้ายกันและฉันเห็นว่าฉันต้องมองเข้าไปในการออกแบบของฉันอย่างลึกซึ้งยิ่งขึ้นและปรับมันเพื่อที่ฉันจะได้ไม่ต้องลบผู้สังเกตการณ์อีกต่อไป
Bogdan

ใช้ค่าบูลเหมือนที่แนะนำในคำตอบนี้ทำงานได้ดีที่สุดสำหรับฉัน: stackoverflow.com/a/37641685/4833705
Lance Samaria

คำตอบ:


315

ลองจับการโทรของคุณออก

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

12
1+ คำตอบที่ดีทำงานให้ฉันและฉันเห็นด้วยกับคำพูดของคุณก่อนที่จะได้รับการแก้ไข
Robert

25
upvoted สำหรับคำพูดที่ถูกลบที่ฉันน่าจะเห็นด้วย
Ben Gotow

12
ไม่ได้อยู่ที่นี่โซลูชั่นที่สง่างามอื่น ๆ ? อันนี้ใช้เวลาอย่างน้อย 2 มิลลิวินาทีต่อการใช้งาน ... ลองจินตนาการดูใน tableviewcell
João Nunes

19
ลดระดับลงเนื่องจากคุณไม่ได้ระบุว่าไม่ปลอดภัยสำหรับรหัสการผลิตและมีแนวโน้มที่จะล้มเหลวเมื่อใด การเพิ่มข้อยกเว้นผ่านรหัสเฟรมเวิร์กไม่ใช่ตัวเลือกในโกโก้
Nikolai Ruhe

6
วิธีใช้รหัสนี้ใน swift 2.1 ทำ {ลอง self.playerItem? .removeObserver (ตัวเอง forKeyPath: "status")} จับข้อผิดพลาดให้เป็น NSError {print (error.localizedDescription)} ได้รับคำเตือน
Vipulk617

37

คำถามจริงคือทำไมคุณไม่ทราบว่าคุณกำลังสังเกตหรือไม่

หากคุณกำลังทำสิ่งนี้ในคลาสของวัตถุที่กำลังสังเกตอยู่ให้หยุด อะไรก็ตามที่กำลังเฝ้าสังเกตอยู่ หากคุณตัดการแจ้งเตือนของผู้สังเกตการณ์โดยปราศจากความรู้ให้คาดหวังว่าสิ่งต่าง ๆ จะพัง โดยเฉพาะอย่างยิ่งคาดว่ารัฐผู้สังเกตการณ์จะค้างเนื่องจากไม่ได้รับการปรับปรุงจากวัตถุที่สังเกตเห็นก่อนหน้านี้

หากคุณกำลังทำสิ่งนี้ในคลาสของวัตถุที่สังเกตเพียงแค่จำว่าวัตถุใดที่คุณกำลังสังเกตอยู่ (หรือถ้าคุณสังเกตวัตถุหนึ่งเพียงอย่างเดียว นี่คือการสมมติว่าการสังเกตนั้นเป็นแบบไดนามิกและระหว่างวัตถุสองอย่างที่ไม่เกี่ยวข้องกัน หากผู้สังเกตการณ์เป็นเจ้าของสิ่งที่สังเกตเพียงเพิ่มผู้สังเกตการณ์หลังจากที่คุณสร้างหรือรักษาสิ่งที่สังเกตและเอาผู้สังเกตการณ์ออกก่อนที่คุณจะปล่อยสิ่งที่สังเกต

การเพิ่มและลบวัตถุในฐานะผู้สังเกตการณ์มักจะเกิดขึ้นในชั้นเรียนของผู้สังเกตการณ์และไม่ควรอยู่ในวัตถุที่สังเกตได้


14
ใช้กรณี: คุณต้องการลบผู้สังเกตการณ์ใน viewDidUnload และใน dealloc นี่เป็นการลบออกสองครั้งและจะโยนข้อยกเว้นหาก viewController ของคุณถูกยกเลิกการโหลดจากคำเตือนหน่วยความจำแล้วปล่อยออกมา คุณแนะนำให้จัดการสถานการณ์นี้อย่างไร
bandejapaisa

2
@bandejapaisa: สวยมากสิ่งที่ฉันพูดในคำตอบของฉัน: ติดตามว่าฉันกำลังสังเกตและพยายามหยุดสังเกตถ้าฉัน
Peter Hosey

41
ไม่นั่นไม่ใช่คำถามที่น่าสนใจ คุณไม่ควรต้องติดตามสิ่งนี้ คุณควรจะสามารถยกเลิกการลงทะเบียนผู้ฟังทั้งหมดใน dealloc ได้โดยไม่ต้องกังวลว่าคุณเกิดเหตุการณ์ที่ต้องกดรหัสพา ธ ที่มีการเพิ่มหรือไม่ มันควรจะทำงานได้เหมือน removeObserver ของ NSNotificationCenter ซึ่งไม่สนใจว่าคุณมีอยู่จริงหรือไม่ ข้อยกเว้นนี้เป็นเพียงการสร้างข้อบกพร่องที่ไม่มีจะเป็นอย่างอื่นซึ่งเป็นการออกแบบ API ที่ไม่ดี
Glenn Maynard

1
@GlennMaynard: เหมือนที่ฉันพูดในคำตอบ“ ถ้าคุณตัดการแจ้งเตือนของผู้สังเกตการณ์โดยที่ไม่รู้ตัว โดยเฉพาะอย่างยิ่งคาดว่ารัฐผู้สังเกตการณ์จะค้างเนื่องจากไม่ได้รับการปรับปรุงจากวัตถุที่สังเกตเห็นก่อนหน้านี้” ผู้สังเกตการณ์ทุกคนควรยุติการสังเกตตนเอง ความล้มเหลวในการทำเช่นนี้ควรจะมองเห็นได้อย่างสมบูรณ์
Peter Hosey

3
ไม่มีอะไรในคำถามที่พูดถึงการลบผู้สังเกตการณ์ของรหัสอื่น
Glenn Maynard

25

FWIW, [someObject observationInfo]น่าจะเป็นnilถ้าsomeObjectไม่ได้มีผู้สังเกตการณ์ใด ๆ ฉันจะไม่เชื่อพฤติกรรมนี้ แต่ฉันไม่ได้เห็นมันเป็นเอกสาร นอกจากนี้ฉันไม่ทราบวิธีการอ่านobservationInfoเพื่อรับผู้สังเกตการณ์ที่เฉพาะเจาะจง


คุณรู้หรือไม่ว่าฉันสามารถดึงผู้สังเกตการณ์ที่เฉพาะเจาะจงได้อย่างไร objectAtIndex:ไม่ได้ผลลัพธ์ที่ต้องการ)
Eimantas

1
@MattDiPasquale คุณรู้หรือไม่ว่าฉันจะอ่าน observInfo ในรหัสได้อย่างไร ในการพิมพ์มันออกมาได้ดี แต่มันก็เป็นตัวชี้ให้เป็นโมฆะ ฉันควรอ่านมันอย่างไร?
neeraj

สังเกตวิธีการตรวจแก้จุดบกพร่องเอกสารในเอกสารการแก้จุดบกพร่องของ Xcode (สิ่งที่มี "วิเศษ" ในชื่อเรื่อง) คุณสามารถลองดูได้ ฉันสามารถบอกได้ว่าถ้าคุณจำเป็นต้องรู้ว่ามีใครบางคนกำลังสังเกตวัตถุของคุณ - คุณกำลังทำอะไรผิดพลาด คิดใหม่สถาปัตยกรรมและตรรกะของคุณ เรียนรู้วิธีที่ยากลำบาก)
Eimantas

ที่มา:NSKeyValueObserving.h
nefarianblack

บวก 1 สำหรับจุดจบที่น่าขบขัน แต่ก็ยังมีคำตอบที่เป็นประโยชน์บ้าง
Will Von Ullrich

4

วิธีเดียวที่จะทำเช่นนี้คือการตั้งค่าสถานะเมื่อคุณเพิ่มผู้สังเกตการณ์


3
คุณจะจบลงด้วย BOOLs ทุกที่ดีกว่ายังคงสร้างวัตถุห่อหุ้ม KVO ที่จัดการเพิ่มผู้สังเกตการณ์และลบมัน สามารถตรวจสอบให้แน่ใจว่าผู้สังเกตการณ์ของคุณถูกลบเพียงครั้งเดียว เราใช้วัตถุแบบนี้และใช้งานได้
bandejapaisa

ความคิดที่ดีถ้าคุณไม่ได้สังเกต
อังเดร Simon

4

เมื่อคุณเพิ่มผู้สังเกตการณ์ไปยังวัตถุคุณสามารถเพิ่มลงในแบบNSMutableArrayนี้:

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

หากคุณต้องการคลายวัตถุคุณสามารถทำสิ่งที่ชอบ:

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

โปรดจำไว้ว่าถ้าคุณไม่ทำการกู้คืนวัตถุเดียวให้ลบออกจาก_observedObjectsอาร์เรย์:

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

1
หากสิ่งนี้เกิดขึ้นในโลกหลายเธรดคุณต้องตรวจสอบให้แน่ใจว่าอาเรย์ของคุณเป็น ThreadSafe
shrutim

คุณกำลังทำการอ้างอิงที่แข็งแกร่งของวัตถุซึ่งจะเพิ่มจำนวนการเก็บรักษาทุกครั้งที่มีการเพิ่มวัตถุในรายการและจะไม่ถูกจัดสรรคืนเว้นแต่ว่าการอ้างอิงนั้นจะถูกลบออกจากอาร์เรย์ ฉันต้องการใช้NSHashTable/ NSMapTableเพื่อเก็บการอ้างอิงที่อ่อนแอ
atulkhatri

3

ในความคิดของฉัน - งานนี้คล้ายกับกลไกการเก็บเงิน คุณไม่สามารถแน่ใจได้ว่าในขณะนี้คุณมีผู้สังเกตการณ์ แม้ว่าคุณจะตรวจสอบ: self.observationInfo - คุณไม่สามารถแน่ใจได้ว่าคุณจะมี / ไม่มีผู้สังเกตการณ์ในอนาคต

เช่นเดียวกับretainCount บางทีobservationInfoวิธีไม่ว่าชนิดของไร้ประโยชน์ที่ แต่ฉันเท่านั้นที่ใช้ในวัตถุประสงค์การแก้ปัญหา

ดังนั้นคุณต้องทำเช่นนี้ในการจัดการหน่วยความจำ หากคุณเพิ่มผู้สังเกตการณ์ให้ลบออกเมื่อไม่ต้องการ ชอบใช้วิธีการ viewWillAppear / viewWillDisappear ฯลฯ เช่น:

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

และคุณต้องมีการตรวจสอบเฉพาะ - ใช้คลาสของคุณเองที่จัดการอาร์เรย์ของผู้สังเกตการณ์และใช้สำหรับการตรวจสอบ


[self removeObserver:nil forKeyPath:@""]; ต้องไปก่อน: [super viewWillDisappear:animated];
Joshua Hart

@JoshuaHart ทำไม?
quarezz

เพราะมันเป็นวิธีการฉีกขาด (dealloc) เมื่อคุณลบล้างวิธีการฉีกขาดบางอย่างคุณจะเรียกว่าวิธีสุดท้าย ไลค์: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

viewWillDisapearไม่ใช่วิธีการฉีกขาดและไม่มีการเชื่อมต่อกับ dealloc หากคุณผลักดันไปข้างหน้าไปยังกองนำทางการนำทางviewWillDisapearจะถูกเรียกใช้ แต่มุมมองของคุณจะยังคงอยู่ในหน่วยความจำ ฉันเห็นว่าคุณกำลังจะไปกับตรรกะของการติดตั้ง / การฉีกขาด แต่การทำที่นี่จะไม่ให้ประโยชน์ที่แท้จริง คุณต้องการที่จะลบก่อนที่จะ super เฉพาะในกรณีที่คุณมีตรรกะบางอย่างในคลาสฐานที่อาจขัดแย้งกับผู้สังเกตการณ์ปัจจุบัน
quarezz

3

[someObject observationInfo]ส่งคืนnilถ้าไม่มีผู้สังเกตการณ์

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

}

ตามเอกสารของ Apple: observInfo จะส่งคืนตัวชี้ที่ระบุข้อมูลเกี่ยวกับผู้สังเกตการณ์ทั้งหมดที่ลงทะเบียนกับผู้รับ
FredericK

นี่เป็นการกล่าวที่ดีกว่าในคำตอบของ @ mattdipasquale
Ben Leggiero

2

จุดรวมของรูปแบบการสังเกตการณ์คือการอนุญาตให้คลาสที่สังเกตได้นั้นถูก "ปิดผนึก" - ไม่ทราบหรือไม่สนใจว่ามันถูกสังเกตหรือไม่ คุณกำลังพยายามแบ่งรูปแบบนี้อย่างชัดเจน

ทำไม?

ปัญหาที่คุณมีคือคุณกำลังสมมติว่าคุณกำลังถูกสังเกตเมื่อคุณไม่ได้ วัตถุนี้ไม่ได้เริ่มการสังเกต หากคุณต้องการให้คลาสของคุณมีการควบคุมกระบวนการนี้คุณควรพิจารณาใช้ศูนย์การแจ้งเตือน วิธีนี้ทำให้คลาสของคุณควบคุมได้อย่างสมบูรณ์เมื่อสามารถสังเกตข้อมูลได้ ดังนั้นจึงไม่สนใจว่าใครกำลังรับชมอยู่


10
เขาถามว่าผู้ฟังสามารถรู้ได้อย่างไรว่ามันกำลังฟังอะไรอยู่หรือเปล่า
Glenn Maynard

1

ฉันไม่ใช่แฟนตัวยงของโซลูชันลองจับดังนั้นสิ่งที่ฉันทำเกือบตลอดเวลาคือฉันสร้างวิธีการสมัครและยกเลิกการสมัครสำหรับการแจ้งเตือนเฉพาะภายในชั้นเรียนนั้น ตัวอย่างเช่นทั้งสองวิธี subcribe หรือยกเลิกการเป็นสมาชิกวัตถุไปยังการแจ้งเตือนแป้นพิมพ์ทั่วโลก:

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

ภายในวิธีการเหล่านั้นฉันใช้ทรัพย์สินส่วนตัวที่ตั้งค่าเป็นจริงหรือเท็จขึ้นอยู่กับสถานะการสมัครสมาชิกเช่น:

@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

นอกจากคำตอบของอดัมแล้วฉันอยากจะแนะนำให้ใช้มาโครแบบนี้

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

ตัวอย่างการใช้งาน

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

1
มันบ้าแค่ไหนที่ทำให้เกิดข้อยกเว้น? ทำไมมันไม่ทำอะไรเลยถ้าไม่มีสิ่งที่แนบมา?
Aran Mulholland
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.