Làm thế nào tôi có thể biết nếu một đối tượng có một người quan sát giá trị chính được đính kèm


142

nếu bạn nói với một đối tượng c mục tiêu để removeObservers: cho một đường dẫn chính và đường dẫn chính đó chưa được đăng ký, nó sẽ phá vỡ những nỗi buồn. giống -

'Không thể xóa người quan sát cho đường dẫn chính "theKeyPath" vì nó không được đăng ký làm người quan sát.'

Có cách nào để xác định xem một đối tượng có người quan sát đã đăng ký không, vì vậy tôi có thể làm điều này

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

Tôi đã tham gia vào kịch bản này khi cập nhật một ứng dụng cũ trên iOS 8 trong đó bộ điều khiển chế độ xem đang bị xử lý và ném ngoại lệ "Không thể xóa". Tôi nghĩ rằng bằng cách gọi addObserver:trong viewWillAppear:và tương ứng removeObserver:trong viewWillDisappear:, các cuộc gọi được kết hợp một cách chính xác. Tôi phải khắc phục nhanh vì vậy tôi sẽ triển khai giải pháp thử bắt và để lại nhận xét để điều tra nguyên nhân hơn nữa.
tạm biệt

Tôi chỉ đang xử lý một cái gì đó tương tự và tôi thấy tôi cần nhìn sâu hơn vào thiết kế của mình và điều chỉnh nó để tôi sẽ không cần phải loại bỏ người quan sát nữa.
Bogdan

sử dụng giá trị bool như được đề xuất trong câu trả lời này có hiệu quả tốt nhất đối với tôi: stackoverflow.com/a/37641685/4833705
Lance Samaria

Câu trả lời:


315

Đặt một thử bắt xung quanh cuộc gọi removeObserver của bạn

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

12
1+ Câu trả lời tốt, đã làm việc cho tôi và tôi đồng ý với câu nói của bạn trước khi nó được chỉnh sửa.
Robert

25
upvote cho rant đã xóa mà tôi rất có thể đồng ý với.
Ben Gotow

12
Không có bất kỳ giải pháp thanh lịch nào khác? cái này mất ít nhất 2ms cho mỗi lần sử dụng ... hãy tưởng tượng nó trong một cái bàn
João Nunes

19
Bị từ chối vì bạn đang bỏ qua để nói rằng điều này không an toàn cho mã sản xuất và có khả năng bị lỗi bất cứ lúc nào. Tăng ngoại lệ thông qua mã khung không phải là một lựa chọn trong Ca cao.
Nikolai Ruhe

6
Cách sử dụng mã này trong swift 2.1. do {thử self.playerItem? .removeObserver (self, forKeyPath: "status")} bắt lỗi khi NSError {print (error.localizedDes mô tả)} nhận cảnh báo.
Vipulk617

37

Câu hỏi thực sự là tại sao bạn không biết liệu bạn có quan sát hay không.

Nếu bạn đang làm điều này trong lớp của đối tượng được quan sát, hãy dừng lại. Bất cứ điều gì đang quan sát nó sẽ tiếp tục quan sát nó. Nếu bạn cắt bỏ thông báo của người quan sát mà không có kiến ​​thức, hãy hy vọng mọi thứ sẽ bị phá vỡ; cụ thể hơn, hy vọng trạng thái của người quan sát sẽ bị cũ vì nó không nhận được cập nhật từ đối tượng được quan sát trước đây.

Nếu bạn đang làm điều này trong lớp của đối tượng quan sát, chỉ cần nhớ những đối tượng bạn đang quan sát (hoặc, nếu bạn chỉ quan sát một đối tượng, cho dù bạn đang quan sát nó). Điều này giả định rằng sự quan sát là động và giữa hai đối tượng không liên quan; nếu người quan sát sở hữu người quan sát, chỉ cần thêm người quan sát sau khi bạn tạo hoặc giữ lại người quan sát và xóa người quan sát trước khi bạn thả người quan sát.

Việc thêm và xóa một đối tượng như một người quan sát thường sẽ xảy ra trong lớp của người quan sát và không bao giờ trong đối tượng được quan sát.


14
Ca sử dụng: Bạn muốn xóa các trình quan sát trong viewDidUnload và cả trong dealloc. Điều này sẽ loại bỏ chúng hai lần và sẽ ném ngoại lệ nếu viewContoder của bạn được tải khỏi cảnh báo bộ nhớ, và sau đó cũng được phát hành. Làm thế nào để bạn đề nghị xử lý tình huống này?
bandejapaisa

2
@bandejapaisa: Khá nhiều điều tôi đã nói trong câu trả lời của mình: Theo dõi xem tôi có đang quan sát không và chỉ cố gắng ngừng quan sát nếu tôi là.
Peter Hosey

41
Không, đó không phải là một câu hỏi thú vị. Bạn không cần phải theo dõi điều này; bạn có thể đơn giản hủy đăng ký tất cả người nghe trong dealloc, mà không cần quan tâm đến việc bạn có tình cờ nhấn vào đường dẫn mã nơi nó được thêm vào hay không. Nó sẽ hoạt động giống như removeObserver của NSNotificationCenter, không quan tâm bạn có thực sự có hay không. Ngoại lệ này chỉ đơn giản là tạo ra các lỗi trong đó không có lỗi nào tồn tại, đó là thiết kế API xấu.
Glenn Maynard

1
@GlennMaynard: Giống như tôi đã nói trong câu trả lời, nếu bạn cắt bỏ thông báo của người quan sát mà không có kiến ​​thức, hãy hy vọng mọi thứ sẽ bị phá vỡ; cụ thể hơn, hy vọng trạng thái của người quan sát sẽ bị cũ vì nó không nhận được cập nhật từ đối tượng được quan sát trước đây. Mỗi người quan sát nên kết thúc quan sát riêng của mình; thất bại để làm điều này lý tưởng nên được nhìn thấy cao.
Peter Hosey

3
Không có gì trong câu hỏi nói về việc loại bỏ các quan sát viên của mã khác .
Glenn Maynard

25

FWIW, [someObject observationInfo]dường như là nilnếu someObjectkhông có bất kỳ nhà quan sát nào. Tôi sẽ không tin tưởng hành vi này, tuy nhiên, vì tôi chưa thấy nó được ghi lại. Ngoài ra, tôi không biết cách đọc observationInfođể có được những người quan sát cụ thể.


Bạn có tình cờ biết làm thế nào tôi có thể lấy một người quan sát cụ thể không? objectAtIndex:không mang lại kết quả mong muốn.)
Eimantas

1
@MattDiPasquale Bạn có biết làm thế nào tôi có thể đọc obsInfo trong mã không? Trong các bản in, nó được phát hành tốt, nhưng nó là một con trỏ để trống. Tôi nên đọc nó như thế nào?
neeraj

obsInfo là phương pháp gỡ lỗi được ghi lại trong giấy gỡ lỗi của Xcode (một cái gì đó có "ma thuật" trong tiêu đề). Bạn có thể thử tìm kiếm nó. Tôi có thể nói rằng nếu bạn cần biết nếu ai đó đang quan sát đối tượng của bạn - bạn đang làm gì đó sai. Suy nghĩ lại về kiến ​​trúc và logic của bạn. Đã học nó một cách khó khăn.)
Eimantas

Nguồn:NSKeyValueObserving.h
nefarianblack

cộng 1 cho một kết thúc hài hước nhưng vẫn có phần trả lời hữu ích
Will Von Ullrich

4

Cách duy nhất để làm điều này là đặt cờ khi bạn thêm người quan sát.


3
Bạn kết thúc với BOOL ở khắp mọi nơi, tốt hơn là vẫn tạo một đối tượng bao bọc KVO xử lý thêm trình quan sát và loại bỏ nó. Nó có thể đảm bảo người quan sát của bạn chỉ bị xóa một lần. Chúng tôi đã sử dụng một đối tượng như thế này và nó hoạt động.
bandejapaisa

ý tưởng tuyệt vời nếu bạn không luôn luôn quan sát.
Andre Simon

4

Khi bạn thêm một người quan sát vào một đối tượng, bạn có thể thêm nó vào NSMutableArraynhư thế này:

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

Nếu bạn muốn không quan tâm đến các đối tượng, bạn có thể làm một cái gì đó như:

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

Hãy nhớ rằng, nếu bạn không quan tâm đến một đối tượng sẽ xóa nó khỏi _observedObjectsmảng:

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

1
Nếu điều này xảy ra trong một thế giới đa luồng, bạn cần chắc chắn rằng mảng của bạn là ThreadSafe
shrutim

Bạn đang giữ một tham chiếu mạnh mẽ của một đối tượng, điều này sẽ làm tăng số lần giữ lại mỗi khi một đối tượng được thêm vào danh sách và sẽ không bị hủy bỏ trừ khi tham chiếu của nó bị xóa khỏi mảng. Tôi muốn sử dụng NSHashTable/ NSMapTableđể giữ các tài liệu tham khảo yếu.
atulkhatri

3

Theo tôi - điều này hoạt động tương tự như cơ chế retCount. Bạn không thể chắc chắn rằng tại thời điểm hiện tại bạn có người quan sát của bạn. Ngay cả khi bạn kiểm tra: self.observationInfo - bạn không thể biết chắc chắn rằng mình sẽ có / sẽ không có người quan sát trong tương lai.

Giống như keepCount . Có lẽ observationInfo phương pháp là không chính xác rằng loại vô dụng, nhưng tôi chỉ sử dụng nó trong các mục đích gỡ lỗi.

Vì vậy, kết quả là - bạn chỉ cần làm điều đó như trong quản lý bộ nhớ. Nếu bạn đã thêm một người quan sát - chỉ cần xóa nó khi bạn không cần nó. Giống như sử dụng các phương thức viewWillAppear / viewWillDisappear, v.v. Ví dụ:

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

Và bạn cần một số kiểm tra cụ thể - thực hiện lớp của riêng bạn xử lý một mảng các trình quan sát và sử dụng nó cho các kiểm tra của bạn.


[self removeObserver:nil forKeyPath:@""]; cần phải đi trước: [super viewWillDisappear:animated];
Joshua Hart

@JoshuaHart tại sao?
quarezz

Bởi vì đó là một phương pháp phá bỏ (dealloc). Khi bạn ghi đè một số phương thức phân tích, bạn gọi siêu cuối cùng. Giống như: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

viewWillDisapear không phải là một phương thức phá bỏ và nó không có kết nối với dealloc. Nếu bạn đẩy về phía trước ngăn xếp điều hướng, viewWillDisapear sẽ được gọi, nhưng chế độ xem của bạn sẽ ở trong bộ nhớ. Tôi thấy bạn đang đi đâu với logic thiết lập / phân tích, nhưng thực hiện nó ở đây sẽ không mang lại lợi ích thực sự. Bạn sẽ muốn loại bỏ trước siêu chỉ khi bạn có một số logic trong lớp cơ sở, điều đó có thể xung đột với người quan sát hiện tại.
quarezz

3

[someObject observationInfo]trả lại nilnếu không có người quan sát.

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

}

Theo tài liệu của Apple: obsInfo trả về một con trỏ xác định thông tin về tất cả các quan sát viên đã đăng ký với người nhận.
FredericK

Điều này được nói tốt hơn trong câu trả lời của @ mattdipasquale
Ben Leggiero

2

Toàn bộ quan điểm của mẫu quan sát là cho phép một lớp quan sát được "niêm phong" - không biết hoặc không quan tâm liệu nó có được quan sát hay không. Bạn rõ ràng đang cố gắng để phá vỡ mô hình này.

Tại sao?

Vấn đề bạn gặp phải là bạn cho rằng bạn đang bị quan sát khi bạn không. Đối tượng này đã không bắt đầu quan sát. Nếu bạn muốn lớp của bạn có quyền kiểm soát quá trình này, thì bạn nên xem xét sử dụng trung tâm thông báo. Bằng cách đó, lớp của bạn có toàn quyền kiểm soát khi dữ liệu có thể được quan sát. Do đó, nó không quan tâm ai đang xem.


10
Anh ấy hỏi làm thế nào người nghe có thể phát hiện ra nếu nó đang nghe một cái gì đó, chứ không phải làm thế nào đối tượng được quan sát có thể tìm ra nếu nó được quan sát.
Glenn Maynard

1

Tôi không phải là một fan hâm mộ của giải pháp thử bắt đó, vì vậy, điều tôi làm hầu hết thời gian là tôi tạo ra một phương thức đăng ký và hủy đăng ký cho một thông báo cụ thể trong lớp đó. Ví dụ: hai phương thức này đăng ký hoặc hủy đăng ký đối tượng vào thông báo bàn phím toàn cầu:

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

Trong các phương thức đó, tôi sử dụng một thuộc tính riêng được đặt thành đúng hoặc sai tùy thuộc vào trạng thái đăng ký như vậy:

@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

Ngoài câu trả lời của Adam, tôi muốn đề nghị sử dụng macro như thế này

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

ví dụ về việc sử dụng

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

1
Làm thế nào điên là nó ném một ngoại lệ? Tại sao nó không làm gì nếu không có gì được đính kèm?
Aran Mulholland
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.