iOS通知中心升级 -可设置按优先级执行block

简单介绍下,这是需求驱动中发现iOS的NotificationCenter有很多功能无法实现,于是对其进行了一层包装。相当于手动管理观察者栈和监听者期望执行的事件,因此可以为其添加了很多新增的功能,将其命名为MessageTransfer。

一.核心优点

1.高度解耦

  • 生命周期与页面实例周期相隔离
  • 可实现跨组件间通讯
  • 业务无关,内部只关心block代码执行

2.灵活定制

  • 每一条信息在发送的时候可以设置同步或异步执行
  • 支持消息的内部处理操作,内部处理操作后将结果返回
  • 一个消息有多个接收者时可以通过优先级排序执行。(同步情况下)
  • 同一个消息同一个实例可以实现多个block,并且可以是普通block+处理block

3.使用简便

  • 接口清晰符合逻辑设定,block挂在一起,代码聚合式管理
  • 内部实现一些连带操作,使用时或修改时都只用修改一处,以前则需要需求一变改多处
  • 严格把控循环引用无法释放等情况,内部实现了观察者delloc时的移除

 

二.API设计

1.以前的API使用

// ********普通做法    
// 1.一边发送  (这个通知的名字命名还需要注意统一)
[[NSNotificationCenter defaultCenter]postNotificationName:@"XXX" object:XXX userInfo:XXX];
// 2.另一边接收
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(xxx:) name:@"XXX" object:XXX]
// 3.还要手动去实现一个方法
XXX:
// 4.在自己方法的delloc时还要记得将观察者移除,否则会导致崩溃。
delloc:  [NSNotificationCenter defaultCenter]removeObserver

2.MessageTransfer API设计

// ********MessageTransfer API设计
//下面方法的复杂度由杂至简,只贴了最复杂方法的注释
/**
 *  add a block also add observer with priority,when msg received, and do some processing when msg received,return the results
 *
 *  @param msg         origin msg
 *  @param interaction include observer and priority
 *  @param block       doing onReceived,and return the processing results
 */
- (void)listenMsg:(NSString *)msg withInteraction:(SXMessageInteraction *)interaction onReceiveAndProcessing:(MsgPosterReturnAction)block;
- (void)listenMsg:(NSString *)msg withInteraction:(SXMessageInteraction *)interaction onReceive:(MsgPosterVoidAction)block;
- (void)listenMsg:(NSString *)msg observer:(id)observer onReceiveAndProcessing:(MsgPosterReturnAction)block;
- (void)listenMsg:(NSString *)msg observer:(id)observer onReceive:(MsgPosterVoidAction)block;
 
/**
 *  send a msg with a object and set this msg's excute type ,and do the block when msg on reached,
 *
 *  @param msg    origin msg
 *  @param object msg carry object
 *  @param type   (async or sync default is sync)
 *  @param block  doing on reached
 */
- (void)sendMsg:(NSString *)msg withObject:(id)object type:(SXMessageExcuteType)type onReached:(MsgPosterVoidAction)block;
- (void)sendMsg:(NSString *)msg withObject:(id)object onReached:(MsgPosterVoidAction)block;
- (void)sendMsg:(NSString *)msg onReached:(MsgPosterVoidAction)block;
 

三.流程结构

上图大致画出了,实例监听消息,同步消息发送时所产生的事件联动原理。 包括消息和观察者注册后的压栈存储,transfer内部对同步异步判断后所采用的不同执行策略,观察者的按优先级排序, 需要内部处理的block 通过发送者的msgObject作为入参执行block后返回值作为发送者block的入参继续执行,当一个实例销毁时,在观察者栈里将其移除。(董铂然博客园)

 

四.实际使用

// ********观察者A (普通监听)
[MsgTransfer listenMsg:@"DSXDSX"  onReceive:^(id msgObject) {
    MTLog(@"*******最普通的监听回调,参数内容%@",msgObject);
}];  
 
  
// ********观察者B (复杂监听)
[MsgTransfer listenMsg:@"DSXDSX" withInteraction:[SXMessageInteraction interactionWithObserver:self priority:@(700)] onReceiveAndProcessing:^id (id dict) {
    MTLog(@"*******优先级是700的block执行-参数%@",dict);
    // 假设对传入的dict做了处理后返回一个字典
    BOOL loginSuccess = [dict[@"pwd"] isEqualToString:@"123456"] && [dict[@"account"] isEqualToString:@"admin"];
    return @{@"result":(loginSuccess?@"登录成功,即将跳转...":@"账号或密码有个不对")};
}];
  
// ********发送者 (同步执行)
[MsgTransfer sendMsg:@"DSXDSX" withObject:@{@"account":@"admin",@"pwd":@"123456"} type:SXMessageExcuteTypeSync onReached:^(id obj) {
    if ([obj isKindOfClass:[NSDictionary class]]) {
        MTLog(@"一个内部处理后的回调  *****%@",obj[@"result"]);
    }else{
        MTLog(@"一个普通者的回调  *****消息ID%@",obj);
    }
}];
  
// 然后就.. 没了

大概的实现了一个登录逻辑,发送的消息中的object带上了登录信息,负责登录的类接收到了消息之后对参数进行了判断或其他处理将结果返回,这个block的返回值会作为发送者block的入参。也就是说发送登录信息的类在这个消息的block中就能够拿到登录结果。 这些都是以往的消息中心所不能做到的。

 

五.源码片段

#pragma mark -
#pragma mark listen recieved

- (void)workingOnReceived:(NSNotification *)object{
    NSString *name = object.name;
    
    SXMessageExcuteType excuteType = [[self.msgExcuteType objectForKey:name]integerValue];
    
    NSArray *observerArray = [self.msgObserversStack valueForKey:name];
    if (excuteType == SXMessageExcuteTypeSync) {
        NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"_priority" ascending:NO];
        observerArray = [observerArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    }
    
    for (SXMessageObserver *obs in observerArray) {
        NSArray *voidBlocks = [self.blockReceivedVoidStack valueForKey:obs.msgName];
        NSArray *returnBlocks = [self.blockReceivedReturnStack valueForKey:obs.msgName];
        
        if(voidBlocks && (voidBlocks.count > 0)){
            for (id voidBlock in voidBlocks) {
                if (excuteType == SXMessageExcuteTypeSync){
                    [self excuteWithVoidBlockDict:@{@"obs":obs,@"object":object,@"block":voidBlock}];
                }else if (excuteType == SXMessageExcuteTypeAsync){
                    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(excuteWithVoidBlockDict:) object:@{@"obs":obs,@"object":object,@"block":voidBlock}];
                    [self.msgQueue addOperation:operation];
                }
            }
        }
        
        if (returnBlocks && (returnBlocks.count >0)){
            for (id returnBlock in returnBlocks) {
                if (excuteType == SXMessageExcuteTypeSync){
                    [self excuteWithReturnBlockDict:@{@"obs":obs,@"object":object,@"block":returnBlock}];
                }else if (excuteType == SXMessageExcuteTypeAsync){
                    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(excuteWithReturnBlockDict:) object:@{@"obs":obs,@"object":object,@"block":returnBlock}];
                    [self.msgQueue addOperation:operation];
                }
            }
        }

        if(returnBlocks.count + voidBlocks.count < 1){
#if TEST || DEBUG
            NSString *errormsg = [NSString stringWithFormat:@"dsxWARNING! this msg <%@> not binding Recieved block",obs.msgName];
            NSLog(@"%@",errormsg);
#endif
        }
    }
}

这上面就是观察者的block即将执行的方法,其实原理很简单就是库里自己设置了很多的栈用来存储不能类别的block和观察者。并且以前的观察者可能是A或B或C,现在的观察者统一汇总到MessageTransfer。由这个中转站来控制观察者执行block。下面的一个方法就是前面所说的监听者把处理结果返回给发送者的block作为入参。

- (void)excuteWithReturnBlockDict:(NSDictionary *)dict{
    
    SXMessageObserver *obs = dict[@"obs"];
    NSNotification *object = dict[@"object"];
    id block = dict[@"block"];
    
    MsgPosterReturnAction returnBlockRecieved = (MsgPosterReturnAction)block;
    id processingObject = returnBlockRecieved(object.object)?:returnBlockRecieved([NSObject new]);
    MsgPosterVoidAction blockReached = [self.blockReachedStack valueForKey:object.name];
    if (blockReached) {
        // if processingObject is nil .
        blockReached((processingObject?:@"processing result is nil"));
    }else{
#if TEST || DEBUG
        NSString *errormsg = [NSString stringWithFormat:@"dsxWARNING! this msg <%@> not binding Reached block",obs.msgName];
        NSLog(@"%@",errormsg);
#endif
    }
}

 

六.局限性

当然写的这个messageTransfer的使用也是有一些局限性: 如果一个实例中有多个block,那这些block的优先级就会以最后一次设置的为准,同一个实例只能有一个优先级, 不同优先级的block按顺序执行是针对不能实例的观察者而言的。原本想设置的是实例内也能设置顺序优先级,但是发现这样会让数据结构过于复杂,并且通知中心也没这么细的粒度,他们都是对于同一个消息只会绑定一个方法。所以这个局限性暂时还没遇到无法实现的需求。 还有一点局限性就是观察者的移除过程,虽然内部有观察者移除的方法不需要每一个观察者都在自己的delloc移除了,但是也需要一个触发的方法,就是在所有类的父类的delloc发送一条消息即可,如果你说我们有父类我父类就是UIViewController,那就没办法了 只能你用到时就在子类的delloc发消息了。

// 父类的delloc
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter]postNotificationName:@"SXMsgRemoveObserver" object:self];
}
#pragma mark -
#pragma mark remove observer
- (void)removeObserverInObserverStack:(NSNotification  *)no
{
    id observer = no.object;
    if (![self.obsIndex containsObject:@([observer hash])]) return;
    
    NSLog(@"移除观察者--%ld",[observer hash]);
    [self.msgObserversStack enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSMutableArray *marray = (NSMutableArray *)obj;
        
        id temobj = nil;
        for (SXMessageObserver *obs in marray) {
            if ([@(obs.objectID) isEqual:@([observer hash])]) {
                temobj = obs;
            }
        }
        [marray removeObject:temobj];
        [self.msgObserversStack setObject:marray forKey:key];
    }];
} 

 

附图:

下面有两个调试中的log打印,从中可以看出:发送者和监听者的block都可以执行;能执行普通的bock和带返回值的可处理的block;同一个实例可以绑定多个block;同一个类名不同的实例的block也不会发生冲突;同步和异步执行良好没有漏掉log打印。

同步执行

  

 

异步执行

 

这个库暂时还在完善中,后续会多些优化,判空,提示,断言等。

如果有兴趣的可以看源码 https://github.com/dsxNiubility/SXMessageTransfer

posted @ 2016-04-11 16:07  董铂然  阅读(2440)  评论(4编辑  收藏  举报