MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的通知(Notification)中心机制

2022-08-206.8k 阅读

通知中心机制概述

在Objective-C编程中,通知中心(Notification Center)是一种非常重要的机制,它提供了一种广播式的消息传递模式。简单来说,一个对象可以发布通知(post a notification),而其他对该通知感兴趣的对象可以注册监听这些通知(register to receive notifications)。这一机制允许不同的对象之间进行松散耦合的通信,即这些对象不需要相互知晓对方的具体存在,也不需要直接调用彼此的方法,就能够实现信息的交互。

通知中心在很多场景下都极为有用。比如,在一个复杂的iOS应用中,当用户登录成功后,多个不同的模块(如界面更新模块、数据同步模块等)可能都需要得到这个通知并做出相应的反应。如果不使用通知中心机制,这些模块可能需要直接持有用户登录模块的引用,然后在登录成功时显式调用每个模块的方法,这会导致代码的耦合度大幅增加,维护起来也更加困难。而通过通知中心,登录模块只需要发布一个“用户登录成功”的通知,其他感兴趣的模块自行注册监听该通知即可,大大降低了模块之间的耦合度。

通知中心的组成部分

NSNotification类

NSNotification类是通知的核心载体。它包含了通知的名称(一个NSString对象,用于标识通知的类型,比如UIApplicationDidEnterBackground就是系统定义的应用进入后台的通知名称)、发送通知的对象(称为通知的发布者,也就是post通知的那个对象)以及一些额外的信息(以NSDictionary的形式存在,称为用户信息userInfo,可以用于传递一些与通知相关的具体数据)。

下面是创建一个简单NSNotification对象的示例代码:

// 创建通知名称
NSString *notificationName = @"MyCustomNotification";
// 创建通知发布者
id sender = self;
// 创建用户信息字典
NSDictionary *userInfo = @{@"key": @"value"};
// 创建NSNotification对象
NSNotification *notification = [NSNotification notificationWithName:notificationName sender:sender userInfo:userInfo];

在这个示例中,我们定义了一个自定义的通知名称MyCustomNotification,以当前对象self作为通知发布者,并创建了一个包含简单键值对的用户信息字典。通过notificationWithName:sender:userInfo:这个类方法,我们创建了一个NSNotification对象。

NSNotificationCenter类

NSNotificationCenter类是通知中心的实际管理者。它维护了一个通知的注册表,记录了哪些对象对哪些通知感兴趣。应用程序中只有一个默认的通知中心实例,可以通过[NSNotificationCenter defaultCenter]方法获取。

NSNotificationCenter提供了一系列方法来管理通知的发布和接收。比如addObserver:selector:name:object:方法用于注册一个观察者(即对某个通知感兴趣的对象),当指定名称的通知被发布时,通知中心会调用观察者的指定选择器(方法)。postNotification:postNotificationName:object:以及postNotificationName:object:userInfo:等方法用于发布通知。

观察者(Observer)

观察者就是那些注册了对特定通知感兴趣的对象。一个对象要成为观察者,需要向通知中心注册,提供自己的一个方法(选择器),当相应通知发布时,通知中心会调用这个方法。观察者在不再需要接收通知时,应该及时向通知中心注销,否则可能会导致内存泄漏等问题。

注册和接收通知

注册通知

在Objective-C中,一个对象注册接收通知非常简单,只需要调用NSNotificationCenteraddObserver:selector:name:object:方法。下面是一个详细的示例: 假设我们有一个ViewController类,我们希望它能接收一个自定义的通知。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 注册通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"MyCustomNotification" object:nil];
}

- (void)handleNotification:(NSNotification *)notification {
    // 处理通知的逻辑
    NSLog(@"Received notification: %@", notification);
    NSDictionary *userInfo = notification.userInfo;
    if (userInfo) {
        NSLog(@"User info: %@", userInfo);
    }
}

- (void)dealloc {
    // 注销通知
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"MyCustomNotification" object:nil];
}

@end

在上述代码中,在viewDidLoad方法里,我们通过addObserver:self selector:@selector(handleNotification:) name:@"MyCustomNotification" object:nil注册了当前视图控制器self为观察者。当名称为MyCustomNotification的通知发布时,通知中心会调用handleNotification:方法。这里object:nil表示我们对任何对象发布的MyCustomNotification通知都感兴趣,如果指定了某个具体对象,那么只有该对象发布的通知才会被接收。

接收通知

当通知发布后,通知中心会查找所有注册了对该通知感兴趣的观察者,并调用它们的相应方法。在上面的例子中,handleNotification:方法就是接收通知后执行的逻辑。在这个方法中,我们首先打印出接收到的通知对象,然后获取并打印通知携带的用户信息。

发布通知

发布通知也很直接,通过NSNotificationCenter的相关方法即可。还是以上面的ViewController为例,假设我们在某个按钮点击事件中发布通知:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (IBAction)buttonTapped:(id)sender {
    // 创建用户信息字典
    NSDictionary *userInfo = @{@"message": @"Button was tapped"};
    // 发布通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MyCustomNotification" object:self userInfo:userInfo];
}

@end

buttonTapped:方法中,我们首先创建了一个包含“Button was tapped”消息的用户信息字典,然后通过postNotificationName:object:userInfo:方法发布了MyCustomNotification通知,以当前视图控制器self作为发布者,并携带了用户信息。

通知中心的高级特性

通知的优先级

在某些情况下,可能希望某些观察者在接收通知时具有更高的优先级,能够先于其他观察者处理通知。虽然NSNotificationCenter本身并没有直接提供设置通知优先级的功能,但可以通过一些技巧来模拟实现。一种常见的方法是在注册观察者时,将优先级高的观察者注册在前面,这样在通知发布时,它们会先被调用。

通知的传递顺序

通知中心在发布通知时,会按照观察者注册的顺序依次调用观察者的方法。这意味着先注册的观察者会先接收到通知并执行相应逻辑。在实际应用中,需要注意这种顺序可能对业务逻辑产生的影响。例如,如果有两个观察者,一个负责更新数据模型,另一个负责根据数据模型更新界面,那么更新数据模型的观察者应该先注册,以确保界面更新时数据是最新的。

通知的过滤

有时候,可能只希望接收特定条件下的通知。虽然NSNotificationCenter没有提供直接的过滤功能,但可以通过在观察者的处理方法中进行条件判断来实现。比如,在handleNotification:方法中,可以根据通知的发布者或者用户信息中的某些值来决定是否真正处理通知。

- (void)handleNotification:(NSNotification *)notification {
    if ([notification.sender isKindOfClass:[SomeSpecificClass class]]) {
        // 只处理特定类发布的通知
        // 处理通知的逻辑
    }
}

在这个示例中,只有当通知的发布者是SomeSpecificClass类的实例时,才会执行后续的通知处理逻辑。

通知中心与其他通信机制的比较

与代理模式的比较

代理模式是一种一对一的通信方式,一个对象(代理者)代表另一个对象(被代理者)执行某些操作。与通知中心相比,代理模式的耦合度相对较高,因为代理者和被代理者之间需要明确的引用关系。而通知中心是一种广播式的通信,多个对象可以同时接收通知,且发布者和接收者之间不需要直接引用,耦合度更低。

例如,在一个简单的文本输入框场景中,如果使用代理模式,文本输入框对象需要持有代理对象的引用,并在文本改变时调用代理的方法。而如果使用通知中心,文本输入框只需要发布一个“文本改变”的通知,任何对该通知感兴趣的对象(如实时统计字数的模块、根据输入内容更新搜索结果的模块等)都可以接收并处理。

与KVO(Key - Value Observing)的比较

KVO是一种基于属性变化的观察机制,它主要用于观察对象的属性值的改变。通知中心则更侧重于一般性的事件通知。KVO的观察者只能观察到属性值的变化,并且需要提前注册观察特定对象的特定属性。而通知中心可以用于各种自定义的事件,发布者不需要提前知道哪些对象会接收通知,灵活性更高。

比如,一个Person类有一个age属性,如果想观察age属性的变化,使用KVO比较合适。但如果是应用中发生了“网络连接状态改变”这样的事件,使用通知中心来通知各个相关模块会更加合适。

通知中心使用中的常见问题及解决方法

内存泄漏问题

如果一个观察者对象在销毁时没有及时向通知中心注销,通知中心仍然会持有对该观察者的引用,这就可能导致内存泄漏。解决方法是在观察者对象的dealloc方法中调用[[NSNotificationCenter defaultCenter] removeObserver:self name:notificationName object:nil],确保在对象销毁前注销通知。如前面ViewController类的dealloc方法中的示例代码所示。

重复注册问题

重复注册同一个观察者对同一个通知可能会导致通知被多次处理,这可能不符合预期。为了避免重复注册,可以在注册前先检查是否已经注册过。一种简单的方法是在类中定义一个属性来标记是否已经注册,例如:

@interface MyClass () {
    BOOL isRegistered;
}
@end

@implementation MyClass

- (void)registerForNotification {
    if (!isRegistered) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"MyCustomNotification" object:nil];
        isRegistered = YES;
    }
}

@end

通知名称冲突问题

在大型项目中,不同的模块可能会定义相同名称的通知,这会导致冲突。为了避免这种情况,可以采用命名空间的方式,比如在通知名称前加上模块名作为前缀。例如,“com.company.module1.MyCustomNotification”,这样可以大大降低名称冲突的可能性。

实际应用场景中的通知中心

多视图控制器间的通信

在一个包含多个视图控制器的应用中,视图控制器之间有时需要进行通信。例如,在一个导航栏应用中,根视图控制器可能需要知道某个子视图控制器中的某个操作完成。通过通知中心,子视图控制器可以在操作完成时发布一个通知,根视图控制器注册接收该通知并做出相应处理,而不需要直接持有子视图控制器的引用。

数据模型与视图的同步

当数据模型发生变化时,需要及时通知视图进行更新。可以在数据模型类中发布通知,而视图相关的类(如视图控制器或视图本身)注册接收这些通知,然后根据通知携带的信息更新界面。比如,一个待办事项列表应用,当待办事项数据模型中的某个事项被标记为完成时,发布通知,列表视图接收到通知后刷新显示,将该事项以已完成的样式展示。

系统事件的监听

iOS系统会发布许多系统级别的通知,如应用进入后台、前台,内存警告等。应用程序可以注册接收这些通知,并根据需要做出相应处理。例如,当接收到内存警告通知时,释放一些不必要的资源,以避免应用被系统强制关闭。

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

- (void)handleMemoryWarning:(NSNotification *)notification {
    // 释放资源的逻辑
    NSLog(@"Received memory warning. Releasing resources...");
    // 比如释放一些缓存数据等
}

在上述代码中,我们在视图控制器的viewDidLoad方法中注册了对UIApplicationDidReceiveMemoryWarningNotification通知的监听,当接收到内存警告通知时,会在控制台打印信息并执行释放资源的逻辑。

通知中心在多线程环境下的使用

在多线程环境中使用通知中心需要特别小心。因为通知中心的发布和接收操作默认是在当前线程执行的,如果在子线程中发布通知,而观察者的处理方法中有与主线程相关的操作(如更新UI),就会导致问题。

一种解决方法是在观察者的处理方法中使用dispatch_async将与主线程相关的操作放到主线程执行。例如:

- (void)handleNotification:(NSNotification *)notification {
    dispatch_async(dispatch_get_main_queue(), ^{
        // 主线程相关操作,如更新UI
        self.titleLabel.text = @"Notification received";
    });
}

另外,如果在多线程环境下频繁发布通知,可能会导致性能问题。可以考虑使用队列来缓冲通知的发布,避免在短时间内大量发布通知造成系统开销过大。

自定义通知中心

虽然NSNotificationCenter提供了强大的功能,但在某些特定情况下,可能需要自定义通知中心。自定义通知中心可以根据项目的需求进行定制化设计,例如实现更复杂的通知优先级管理、更高效的通知过滤等功能。

实现一个简单的自定义通知中心可以从定义一个管理类开始,该类维护一个通知注册表,记录观察者和通知的关系。然后提供注册、注销和发布通知的方法。以下是一个简单的自定义通知中心的示例代码框架:

#import <Foundation/Foundation.h>

@interface MyNotificationCenter : NSObject

+ (instancetype)defaultCenter;

- (void)addObserver:(id)observer selector:(SEL)selector name:(NSString *)name object:(id)object;
- (void)removeObserver:(id)observer name:(NSString *)name object:(id)object;
- (void)postNotificationName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;

@end

@implementation MyNotificationCenter

static MyNotificationCenter *sharedCenter = nil;

+ (instancetype)defaultCenter {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedCenter = [[MyNotificationCenter alloc] init];
    });
    return sharedCenter;
}

// 存储观察者信息的结构体
typedef struct {
    id observer;
    SEL selector;
} ObserverInfo;

// 用字典存储通知和对应的观察者列表
NSMutableDictionary<NSString *, NSMutableArray<ObserverInfo> *> *notificationRegistry;

- (instancetype)init {
    self = [super init];
    if (self) {
        notificationRegistry = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)addObserver:(id)observer selector:(SEL)selector name:(NSString *)name object:(id)object {
    NSMutableArray<ObserverInfo> *observerList = notificationRegistry[name];
    if (!observerList) {
        observerList = [NSMutableArray array];
        notificationRegistry[name] = observerList;
    }
    ObserverInfo info = {observer, selector};
    [observerList addObject:[NSValue valueWithBytes:&info objCType:@encode(ObserverInfo)]];
}

- (void)removeObserver:(id)observer name:(NSString *)name object:(id)object {
    NSMutableArray<ObserverInfo> *observerList = notificationRegistry[name];
    if (observerList) {
        NSMutableIndexSet *indexSetToRemove = [NSMutableIndexSet indexSet];
        for (NSUInteger i = 0; i < observerList.count; i++) {
            ObserverInfo info;
            [observerList[i] getValue:&info];
            if (info.observer == observer) {
                [indexSetToRemove addIndex:i];
            }
        }
        [observerList removeObjectsAtIndexes:indexSetToRemove];
        if (observerList.count == 0) {
            [notificationRegistry removeObjectForKey:name];
        }
    }
}

- (void)postNotificationName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo {
    NSMutableArray<ObserverInfo> *observerList = notificationRegistry[name];
    if (observerList) {
        for (NSValue *value in observerList) {
            ObserverInfo info;
            [value getValue:&info];
            if ([info.observer respondsToSelector:info.selector]) {
                NSMethodSignature *signature = [info.observer methodSignatureForSelector:info.selector];
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                [invocation setTarget:info.observer];
                [invocation setSelector:info.selector];
                [invocation setArgument:&object atIndex:2];
                [invocation setArgument:&userInfo atIndex:3];
                [invocation invoke];
            }
        }
    }
}

@end

在上述代码中,我们定义了一个MyNotificationCenter类作为自定义通知中心。通过defaultCenter方法实现单例模式,确保应用中只有一个实例。addObserver:selector:name:object:方法用于注册观察者,将观察者信息存储在notificationRegistry字典中。removeObserver:name:object:方法用于注销观察者,从注册表中移除相应的记录。postNotificationName:object:userInfo:方法用于发布通知,遍历注册表中对应通知名称的观察者列表,并调用它们的相应方法。

不过,自定义通知中心虽然灵活,但开发和维护成本相对较高,在实际项目中需要根据具体需求谨慎决定是否使用。

总结

通知中心机制是Objective-C编程中一种强大且灵活的消息传递方式。它通过解耦发布者和接收者之间的直接联系,使得不同模块之间能够更方便地进行通信。从基本的注册、接收和发布通知,到高级特性如优先级处理、过滤等,再到与其他通信机制的比较以及在多线程环境下的使用,通知中心都展现出了其广泛的应用场景和重要性。同时,在使用过程中需要注意避免常见问题,如内存泄漏、重复注册等。了解和掌握通知中心机制,对于开发高质量、可维护的Objective-C应用程序至关重要。无论是在多视图控制器间的通信,还是数据模型与视图的同步,亦或是系统事件的监听等场景中,通知中心都能发挥其独特的作用,帮助开发者构建出更加健壮和灵活的应用架构。