iOS-Notification 实现原理详解

3,879 阅读8分钟
原文链接: www.jianshu.com
目录
  • 一 通知的基本使用
    • 基本概念
    • 什么情况下使用通知
    • 应用场景
    • 如何使用通知
    • 使用通知需要注意的细节
  • 二 通知实现的原理
    • 概述
  • 三 编码实现
一 通知的基本使用
1.1 基本概念

NSNotification是 iOS 中一个调度消息通知的类,采用单例模式,在程序中实现传值,回调等地方,应用很广。在 iOS 中,NSNotificationNSNotificationCenter是使用观察者模式来实现的用于跨层传递消息。

NSNotification声明

@interface NSNotification : NSObject <NSCopying, NSCoding>

@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;

- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@end
1.2 什么情况下使用通知

观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。

1.3 应用场景
  • 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
  • 一个对象必须通知其他对象,而它又不知道其他对象是什么
1.4 如何使用通知
  • 向观察者中心添加观察者(2种方式)
// 观察者接收到通知后执行任务的代码在发送通知的线程中执行
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

// 观察者接收到通知后执行任务的代码在指定的操作队列中执行
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
  • 通知中心向观察者发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
  • 观察者接收到消息执行相应的行为
即执行上面向观察者中心添加观察者中 SEL 方法
  • 在通知中心移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
  • 使用通知时需要注意哪些细节

1.通知一定要移除,在 dealloc 方法中移除,但是 iOS9 之后不再需要手动移除了,详情看 iOS 9 以后通知不再需要手动移除
2.通知有分同步通知和异步通知,只是我们同步通知用的比较多。
3.不能用 - (instancetype)init 初始化一个通知。

二 通知的实现原理

概述 : 首先,信息的传递就依靠通知(NSNotification),也就是说,通知就是信息(执行的方法,观察者本身(self),参数)的包装。

通知中心(NSNotificationCenter)是个单例,向通知中心注册观察者,也就是说,这个通知中心有个集合,这个集合存放着观察者。

那么这个集合是什么样的数据类型 ? 可以这么思考: 发送通知需要name参数,添加观察者也有个name参数,这两个name一样的时候,当发送通知时候,观察者对象就能接受到信息,执行对应的操作。

那么这个集合很容易想到就是NSDictionary!key就是namevalue就是NSArray(存放数据模型),里面存放观察者对象。

image.png

当发送通知时,在通知的字典,根据``name找到value,这个value就是一数组,数组里面存放数据模型(observer、SEL)`。即可执行对应的行为。

三 编码实现

根据NSNotification&NSNotificationCenter接口给出实现代码,创建两个新类CSNotification,CSNotificationCenter,这两个类的接口和苹果提供的接口完全一样。

要点是通知中心是单例类,并且通知中心维护了一个包含所有注册的观察者的集合,这里选择动态数组来存储所有的观察者

3.1 CSNotification 类
  • CSNotification.h
/**
  关于通知实例对象的封装
 */
@interface CSNotification : NSObject
/** name */
@property(nonatomic, copy)NSString *name;
/** object */
@property(nullable, readonly, retain)id object;
/** userInfo */
@property(nullable, readonly, copy)NSDictionary *userInfo;

/**
 初始化方法

 @param name name
 @param object object
 @param userInfo userInfo
 @return 通知实例对象
 */
- (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;

+ (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;

@end
  • CSNotification.m
@implementation CSNotification

- (instancetype)init {
    NSAssert(false, @"do not invoke; not a valid initializer for this class");
    return nil;
}

- (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo {
    self = [super init];
    if (self) {
        _name = name;
        _object = object;
        _userInfo = userInfo;
    }
    return self;
}

+ (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo {
    return [[self alloc] initWithName:name object:object userInfo:userInfo];
}

注意不能使用init初始化,因为缺少必要的参数

3.2 CSNotificationCenter 类
  • CSNotificationCenter.h
@interface CSNotificationCenter : NSObject

/// 单例
+ (CSNotificationCenter *)defaultCenter;

#pragma mark - 添加通知

- (void)addObserver:(nonnull id)observer selector:(nonnull SEL)selector name:(nullable NSString *)name object:(nullable id)object;

- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)object queue:(nullable NSOperationQueue *)queue usingBlock:(void(^)(CSNotification * _Nonnull note))block;

#pragma mark - 接收通知

- (void)postNotification:(CSNotification *)notification;

- (void)postNotificationName:(nonnull NSString *)name object:(nullable id)object;

- (void)postNotificationName:(nonnull NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;

#pragma mark - 移除通知

- (void)removeObserver:(nonnull id)observer;

- (void)removeObserver:(nonnull id)observer name:(nullable NSString *)name object:(nullable id)object;

@end
  • CSNotificationCenter.m

单例的实现

/// 单例
+ (CSNotificationCenter *)defaultCenter {
    static CSNotificationCenter *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.observerJson = [NSMutableDictionary dictionary];
    });
    return instance;
}

定义一个观察者模型用于保存观察者,通知消息名,观察者收到通知后执行代码所在的操作队列和执行代码的回调,模型源码如下:

typedef void(^operationBlock)(CSNotification *notification);

/**
 观察者模型对象
 */
@interface CSObserverModel : NSObject
/** 观察者对象 */
@property(nonatomic, strong)id observer;
/** 执行的方法 */
@property(nonatomic, assign)SEL selector;
/** 通知名字 */
@property(nonatomic, copy)NSString *notificationName;
/** 携带的参数 */
@property(nonatomic, strong)id object;
/** 队列 */
@property(nonatomic, strong)NSOperationQueue *operationQueue;
/** 回调 */
@property(nonatomic, copy)operationBlock block;
@end

声明一个可变字典,用于存储通知模型数据

@interface CSNotificationCenter()

/** observerJson key:NotificationName value:observes */
@property(nonatomic, strong)NSMutableDictionary *observerJson;

@end

向通知中心注册观察者

#pragma mark - 添加通知

- (void)addObserver:(nonnull id)observer selector:(nonnull SEL)selector name:(nullable NSString *)name object:(nullable id)object {
    // 创建数据模型
    CSObserverModel *observerModel = [[CSObserverModel alloc] init];
    observerModel.observer = observer;
    observerModel.selector = selector;
    observerModel.notificationName = name;
    observerModel.object = object;
    
    // 如果不存在,才创建
    if (![self.observerJson objectForKey:name]) {
        NSMutableArray *arrays = [NSMutableArray array];
        
        [arrays addObject:observerModel];
        
        // 添加进 json 中
        [self.observerJson setObject:arrays forKey:name];
    } else {
        // 如果存在,取出来,继续添加进对应数组即可
        NSMutableArray *arrays = (NSMutableArray *)[self.observerJson objectForKey:name];
        
        [arrays addObject:observerModel];
    }
}

- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)object queue:(nullable NSOperationQueue *)queue usingBlock:(void(^)(CSNotification * _Nonnull note))block {
    // 创建数据模型
    CSObserverModel *observerModel = [[CSObserverModel alloc] init];
    observerModel.block = block;
    observerModel.operationQueue = queue;
    observerModel.notificationName = name;
    observerModel.object = object;
    
    // 如果不存在,才创建
    if (![self.observerJson objectForKey:name]) {
        NSMutableArray *arrays = [NSMutableArray array];
        
        [arrays addObject:observerModel];
        
        // 添加进 json 中
        [self.observerJson setObject:arrays forKey:name];
    } else {
        // 如果存在,取出来,继续添加进对应数组即可
        NSMutableArray *arrays = (NSMutableArray *)[self.observerJson objectForKey:name];
        
        [arrays addObject:observerModel];
    }
    
    return nil;
}

发送通知有三种方式

#pragma mark - 发送通知

- (void)postNotification:(CSNotification *)notification {
    // 根据 name 取出对应观察者数组,执行任务
    NSMutableArray *arrays = (NSMutableArray *)[self.observerJson objectForKey:notification.name];
    
    [arrays enumerateObjectsUsingBlock:^( CSObserverModel *observerModel, NSUInteger idx, BOOL *stop) {
        // 取出数据模型
        id observer = observerModel.observer;
        SEL selector = observerModel.selector;
        
        if (!observerModel.operationQueue) {
            // 下面这样写的目的是:手动忽略clang编译器警告
            // 参考:http://blog.csdn.net/qq_18505715/article/details/76087558
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [observer performSelector:selector withObject:notification];
#pragma clang diagnostic pop
        } else {
            // 创建任务
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                // 这里通过 block 回调出去
                observerModel.block(notification);
            }];
            
            // 如果添加观察者 传入 队列,那么任务就放在队列中执行(子线程异步执行)
            [observerModel.operationQueue addOperation:operation];
        }
    }];
}

- (void)postNotificationName:(nonnull NSString *)name object:(nullable id)objec {
    [self postNotificationName:name object:objec userInfo:nil];
};

- (void)postNotificationName:(nonnull NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo {
    CSNotification *notification = [[CSNotification alloc] initWithName:name object:object userInfo:userInfo];
    [self postNotification:notification];
}

移除观察者

#pragma mark - 移除通知

- (void)removeObserver:(nonnull id)observer {
    [self removeObserver:observer name:nil object:nil];
}

- (void)removeObserver:(nonnull id)observer name:(nullable NSString *)name object:(nullable id)object {
    // 移除观察者 - 当有 name 参数时
    if (name.length > 0 && [self.observerJson objectForKey:name]) {
        NSMutableArray *arrays = (NSMutableArray *)[self.observerJson objectForKey:name];
        [arrays removeObject:observer];
    } else {
        // 移除观察者 - 当没有 name 参数时
        if (self.observerJson.allKeys.count > 0 && self.observerJson.allValues.count > 0) {
            NSArray *allKeys = self.observerJson.allKeys;
            
            for (int i = 0; i < allKeys.count; i++) {
                NSMutableArray *keyOfAllObservers = [self.observerJson objectForKey:allKeys[i]];
                
                BOOL isStop = NO;   // 如果找到后就不再遍历后面的数据了
                
                for (int j = 0; j < keyOfAllObservers.count; j++) {
                    // 取出数据模型
                    CSObserverModel *observerModel = keyOfAllObservers[j];
                    
                    if (observerModel.observer == observer) {
                        [keyOfAllObservers removeObject:observerModel];
                        isStop = YES;
                        break;
                    }
                }
                
                if (isStop) {   // 找到了,退出循环
                    break;
                }
            }
        } else {
            NSAssert(false, @"当前通知中心没有观察者");
        }
    }
}
3.3 测试用例
  • 接受通知
- (void)addObserver {
    // 1.使用系统方法添加观察者 - 观察者接收到通知后执行任务的代码在发送通知的线程中执行
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(change:) name:kTextFieldValueChange object:nil];
    
    // 2.使用系统方法添加观察者 - 队列回调 - 观察者接收到通知后执行任务的代码在指定的操作队列中执行
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [[NSNotificationCenter defaultCenter] addObserverForName:kTextFieldValueChange object:nil queue:queue usingBlock:^(NSNotification *note) {
        // 异步回调主线程,与主线程通信
        dispatch_async(dispatch_get_main_queue(), ^{
            [self change:note];
        });
    }];
    
    // 3.添加自定义观察者
    [[CSNotificationCenter defaultCenter] addObserver:self selector:@selector(customChange:) name:kTextFieldValueChange object:nil];
    
    // 4.添加自定义观察者 - 队列回调
    [[CSNotificationCenter defaultCenter] addObserverForName:kTextFieldValueChange object:nil queue:queue usingBlock:^(CSNotification *note) {
        // 异步回调主线程,与主线程通信
        dispatch_async(dispatch_get_main_queue(), ^{
            [self customChange:note];
        });
    }];
}

- (void)change:(NSNotification *)notification {
    NSString *text = (NSString *)notification.object;
    [self.lbe setText:text];
    
    NSLog(@"当前线程是否是主线程: %d",[NSThread isMainThread]);
}

- (void)customChange:(CSNotification *)notification {
    NSString *text = (NSString *)notification.object;
    [self.lbe setText:text];
    
    NSLog(@"当前线程是否是主线程: %d",[NSThread isMainThread]);
}

#pragma mark - dealloc

- (void)dealloc {
    // remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kTextFieldValueChange object:nil];
    [[CSNotificationCenter defaultCenter] removeObserver:self name:kTextFieldValueChange object:nil];
}
  • 发送通知
- (void)tapSend {
    // 发送通知 - 系统
    [[NSNotificationCenter defaultCenter] postNotificationName:kTextFieldValueChange object:self.textF.text];
    
    // 发送通知 - 自定义
    [[CSNotificationCenter defaultCenter] postNotificationName:kTextFieldValueChange object:self.textF.text];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.navigationController popViewControllerAnimated:YES];
    });
}
  • 运行结果如下 - 后期补上

通知接受数据正常


相关文章参考
iOS 9 以后通知不再需要手动移除
iOS Notification实现原理


项目链接地址 - NotificationTheory