iOS如何从通知中心移除观察者:我能一次性为所有观察者调用吗?即使没有观察者存在?

34

我在大多数视图控制器中注册了三个观察者。有些视图控制器有更多,有些视图控制器有更少,但我想在父类中包含部分注册和注销过程。如果没有观察者,调用注销会有问题吗?一次注销对于这三个观察者足够吗?

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillEnterBackground:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    //Has to be unregistered always, otherwise nav controllers down the line will call this method
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

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

没问题,那应该可以。 - rmaddy
2个回答

61

是的,这将删除观察者为 self 的所有注册。在NSNotificationCenter Class Reference中有记录:

以下示例说明如何取消注册所有以前已注册的通知的 someObserver

[[NSNotificationCenter defaultCenter] removeObserver:someObserver];
请注意,在理论上(至少截至iOS 7.0),UIViewController 可能有自己不想在 viewWillDisappear: 中移除的注册。 它不太可能使用 addObserver:selector:name:object: 注册公共 API 中的任何通知,因为这会导致您无法在您的 UIViewController 子类中注册它们,但它现在或将来可能会注册非公共通知。 一个安全的注销方法是为每个注册发送一次 removeObserver:name:object:
- (void)deregisterForKeyboardNotifications {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    [center removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self deregisterForKeyboardNotifications];
}

- (void)dealloc {
    [self deregisterForKeyboardNotifications];
}

另一种方法是使用addObserverForName:object:queue:usingBlock:来注册(而不是addObserver:selector:name:object:)。每次注册都会返回一个新的观察者对象引用。您必须保存它们(也许在一个NSArray实例变量中,如果您不想创建单独的实例变量)。然后将每个观察者对象传递给removeObserver:以注销其通知。例如:

@implementation MyViewController {
    NSMutableArray *observers;
}

- (void)registerForKeyboardNotifications {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    __weak MyViewController *me = self;
    observers = [NSMutableArray array];
    [observers addObject:[center addObserverForName:UIKeyboardWillShowNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me keyboardWillShow:note];
        }]];
    [observers addObject:[center addObserverForName:UIKeyboardWillHideNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me keyboardWillHide:note];
        }]];
    [observers addObject:[center addObserverForName:UIApplicationWillResignActiveNotification
        object:nil queue:queue usingBlock:^(NSNotification *note) {
            [me applicationWillResignActive:note];
        }]];
}

- (void)deregisterForKeyboardNotifications {
    for (id observer in observers) {
        [[NSNotificationCenter defaultCenter] removeObserver:observer];
    }
    observers = nil;
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self deregisterForKeyboardNotifications];
}

- (void)dealloc {
    [self deregisterForKeyboardNotifications];
}

由于addObserverForName:object:queue:usingBlock:返回的每个观察者都是一个新对象,只有一个注册,因此每次调用removeObserver:都只保证移除该观察者的一个注册。

iOS 9 / macOS 10.11及更高版本的更新

从iOS 9和macOS 10.11开始,如果观察者被释放,NSNotificationCenter会自动取消其注册。如果您的部署目标为iOS 9或更高版本或macOS 10.11或更高版本,则不再需要在dealloc方法(或Swift中的deinit)中手动取消注册。


好的,谢谢。但这只是理论吗?还是有合理的危险我可能会删除一些重要的观察者?那么你如何区分你的观察者和iOS观察者呢? - MichiZH
就我所知,自iOS 7.0起,UIViewController实际上并不使用任何通知。我已经更新了我的答案。 - rob mayoff
非常有价值的帖子。非常感谢。如果我按你说的更具体地去掉观察者,例如[center removeObserver:self name:UIKeyboardWillShowNotification object:nil],如果它们从未被添加过,那也不是问题吧? - MichiZH
没问题。 - rob mayoff
有人能解释一下这个和iOS相关的问题吗?我的应用程序因为观察者而崩溃了,但是在我删除观察者之后就不会出现了。我正在将控制器添加为某个视图的观察者。这只适用于“NotificationCenter”吗? - Amber K

7

对于你的第一个问题,即使没有观察者,注销仍然可以。但是对于你删除观察者的方式,[[NSNotificationCenter defaultCenter] removeObserver:someObserver];将删除甚至是超类观察者,这是极不推荐的(除了在dealloc中因为对象被卸载)。但是在viewWillDisappear中,你应该使用[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];逐一删除观察者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接