iOS通知中樞升級!-可設定按優先順序執行block,iosblock
簡單介紹下,這是需求驅動中發現iOS的NotificationCenter有很多功能無法實現,於是對其進行了一層封裝。相當於手動管理觀察者棧和監聽者期望執行的事件,因此可以為其添加了很多新增的功能,將其命名為MessageTransfer。
一.核心優點1.高度解耦
生命週期與頁面執行個體周期相隔離
可實現跨組件間通訊
業務無關,內部只關心block代碼執行
2.靈活定製
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