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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM