iOS-Notification 实现原理详解

作者: 路飞_Luck | 来源:发表于2019-07-13 22:37 被阅读116次
    目录
    • 一 通知的基本使用
      • 基本概念
      • 什么情况下使用通知
      • 应用场景
      • 如何使用通知
      • 使用通知需要注意的细节
    • 二 通知实现的原理
      • 概述
    • 三 编码实现
    一 通知的基本使用
    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

    相关文章

      网友评论

        本文标题:iOS-Notification 实现原理详解

        本文链接:https://www.haomeiwen.com/subject/gxsnhctx.html