iOS消息分發(fā)中心的實現(xiàn)

后端的啟發(fā) && 前端的尷尬

最近一直在看React Native的一些相關(guān)設(shè)計耗跛,對其Redux的設(shè)計模式很感興趣因苹。Redux其實是一種響應(yīng)式的設(shè)計寻馏,跟移動端中的MVVM有點類似兆龙,都是基于對狀態(tài)的監(jiān)聽和綁定驱闷。當然耻台,Redux跟MVVM還是有很大區(qū)別的,Redux的數(shù)據(jù)流是單向的空另,MVVM的不是盆耽。然而,無論是Redux還是MVVM扼菠,都離不開模塊間的消息傳遞摄杂,無論是傳遞數(shù)據(jù)還是傳遞變化狀態(tài)。對后端有一定了解的都知道循榆,后端架構(gòu)非常復雜析恢,其中就包含了消息分發(fā)的功能。

隨著移動端項目規(guī)模越來越大秧饮,模塊間狀態(tài)管理越來越復雜映挂,各個組件間通訊成本越來越高,如果還是采用傳統(tǒng)的Delegate,target-action,Notification,KVO來進行狀態(tài)管理盗尸,那么會使得狀態(tài)管理非常離散柑船,到處都是Delegate代碼。而對于全局狀態(tài)的管理泼各,Delegate就顯得力不從心了鞍时。所以,為了解決越來越高的組件間通訊成本历恐,需要引入一種類似于后端架構(gòu)中的消息分發(fā)器寸癌,用來做消息轉(zhuǎn)發(fā)的中介者,而不是組件和組件間的直接通訊弱贼。簡單說就是組件間通訊的統(tǒng)一管理。

怎么設(shè)計

在iOS中磷蛹,如何設(shè)計一個消息分發(fā)中心呢袄膏?首先要思考以下幾個問題:

  • 一個消息體由什么組成
  • 使用什么方式進行發(fā)送
  • 如何對消息進行訂閱
  • 分發(fā)中心如何分發(fā)消息
  • 是否需要先注冊消息才能發(fā)送
  • 消息太多是否會阻塞
  • 如何減少對代碼的入侵

Dispatch Cener 的設(shè)計

一個消息體柑肴,除了要包含消息內(nèi)容外,還需要一個消息標識,作為消息的唯一標識赌朋,區(qū)分不同消息體。

YKMessage.h


@interface YKMessage : NSObject

/**
 消息唯一標識
 */
@property (nonatomic, readonly) NSString *identify;

/**
 消息內(nèi)容
 */
@property (nonatomic, readonly) id context;

/**
 消息初始化函數(shù)

 @param identify 消息唯一標識
 @param context 內(nèi)容
 @return 消息
 */
- (instancetype)initWithIdentify: (NSString *)identify context: (id)context;

YKMessage.m


@interface YKMessage()<NSCopying>

@property (nonatomic, readwrite) NSString *identify;

@property (nonatomic, readwrite) id context;

@end

@implementation YKMessage

- (instancetype)initWithIdentify: (NSString *)identify context: (id)context {
    self = [super init];
    if (self) {
        _identify = [identify copy];
        _context = [context copy];
    }
    return self;
}


- (id)copyWithZone:(NSZone *)zone {
    YKMessage *message = [[[self class]allocWithZone:zone]init];
    message.identify = self.identify;
    message.context = self.context;
    return message;
}

消息的發(fā)送:首先構(gòu)造消息體审洞,然后通過分發(fā)中心進行發(fā)送


    YKMessage *message = [[YKMessage alloc]initWithIdentify:@"test" context:@[@"1",@"2"]];
    [[YKDispatchCenter shared]dispatchMessage:message];

在需要接收消息的地方訂閱消息:


    [[YKDispatchCenter shared]subscribeWithBinder:self messageIdentify:@"test" handler:^(YKMessage *message, id ext) {
        NSLog(@"dd");
    }];


這里有人會好奇為什么要加入binder這個參數(shù)蚯瞧,綁定self。我解析一下:
每一個消息的訂閱者掂铐,都有自己的生命周期和作用域罕拂,當這個消息訂閱者被釋放后揍异,基于它的消息回調(diào)也應(yīng)該被釋放掉,不應(yīng)再被執(zhí)行爆班。因此回調(diào)是否被執(zhí)行衷掷,就要看綁定者是否被釋放了。

那傳入binder后柿菩,分發(fā)中心怎么知道binder是否已經(jīng)被釋放了戚嗅?

這里就要說一下一個弱應(yīng)用可變數(shù)組NSPointerArray

平時使用NSMutableArrayNSArray的時候枢舶,其數(shù)組元素是不能為一個空值的懦胞,這是因為當數(shù)組元素被add進去的時候,該元素就被數(shù)組持有凉泄,內(nèi)存引用計數(shù)會+1医瘫,而如果元素是空值的話,就會crash或報錯旧困。

但是醇份,使用弱引用數(shù)組就不會有這種問題。弱引用數(shù)組添加元素的時候吼具,會對元素進行一次弱引用僚纷,不會持有該元素,所以不會使元素的內(nèi)存引用發(fā)生變化拗盒,因此即使add進一個空值怖竭,也不會crash或報錯。

所以binder不會被消息分發(fā)中心持有陡蝇,當binder被回收后痊臭,消息中心持有的弱引用數(shù)組中的binder弱引用也會變成空值,在執(zhí)行回調(diào)前就可以通過這個來判斷回調(diào)是否應(yīng)該被執(zhí)行了登夫。

回到第四個問題:分發(fā)中心如何分發(fā)消息广匙?

- (void)dispatchMessage: (YKMessage *)message {
    if (message == nil) {
        return;
    }
    if (![self.registerDictionary.allKeys containsObject:message.identify]) {
        return;
    }
    [self dealMessage:[message copy]];
}

- (void)dealMessage: (YKMessage *)message {
    // 實現(xiàn)異步發(fā)送通知
    dispatch_async(self.serialQueue, ^{
        [[NSNotificationCenter defaultCenter]postNotificationName:message.identify object:message];
    });
}

- (BOOL)registerMessageWithIdentify: (NSString *)messageIdentify {
    if ([self.registerDictionary.allKeys containsObject:messageIdentify]) {
        return NO;
    }
    [self.registerDictionary setValue:@"" forKey:messageIdentify];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(observerHandler:) name:messageIdentify object:nil];
    return YES;
}

- (BOOL)unRegisterMessageWithIdentify: (NSString *)messageIdentify {
    if (![self.registerDictionary.allKeys containsObject:messageIdentify]) {
        return NO;
    }
    [self.registerDictionary removeObjectForKey:messageIdentify];
    [self.actionDictionary removeObjectForKey:messageIdentify];
    [[NSNotificationCenter defaultCenter]removeObserver:self name:messageIdentify object:nil];
    return YES;
}

消息分發(fā)中心是基于通知來實現(xiàn),在注冊的時候?qū)⑼ㄖ陌l(fā)送者和接收者都綁定到自身上恼策。通過發(fā)送通知和接收通知鸦致,來實現(xiàn)消息分發(fā)。

那為什么需要先注冊消息才能發(fā)送呢涣楷?

首先分唾,消息不是隨便發(fā)就能發(fā)的。例如支付模塊中狮斗,支付成功的消息必須是在支付模塊中注冊后才能發(fā)送绽乔,不能隨便哪個模塊就能直接發(fā)送支付成功的消息。在支付模塊加載后注冊支付成功的消息碳褒,在支付模塊卸載后反注冊支付成功消息折砸,這樣就能夠控制消息發(fā)送的權(quán)限了看疗。

其次,性能問題鞍爱。有注冊就有反注冊鹃觉,通過反注冊銷毀不需要維護的消息列表和通知觀察者,減少性能消耗睹逃。

再次盗扇,業(yè)務(wù)問題。比如在某種情況下沉填,不再需要某個消息了疗隶,所有這個消息的回調(diào)都不需要了。這時翼闹,通過反注冊斑鼻,就可以做到。

那消息太多是否會阻塞猎荠?

NSNotificationCenter在主線程中是同步的坚弱,當通知產(chǎn)生時,通知中心會一直等待所有觀察者都收到且處理通知完畢后关摇,才會返回發(fā)送通知的地方繼續(xù)執(zhí)行后面的代碼荒叶。通常來說,如果消息太多输虱,NSNotificationCenter會變慢些楣。然而,這里通過創(chuàng)建一個serialQueue串行隊列宪睹,并將消息的發(fā)送和接收放到這隊列中執(zhí)行愁茁,從而避免主隊列的阻塞等待。


- (void)dealMessage: (YKMessage *)message {
    // 實現(xiàn)異步發(fā)送通知
    dispatch_async(self.serialQueue, ^{
        [[NSNotificationCenter defaultCenter]postNotificationName:message.identify object:message];
    });
}

- (void)observerHandler: (NSNotification *)notification {
    // 實現(xiàn)異步接收通知
    dispatch_async(self.serialQueue, ^{
        YKMessage *object = (YKMessage *)notification.object;
        if (object != nil) {
            NSString *messageIdentify = object.identify;
            [self actionAndCleanWithMessageIdentify:messageIdentify message:object doHandler:YES];
        }
    });
}

如果消息實在太多亭病,還是會對性能有一定影響鹅很,但是這里對發(fā)送和接收通知進行異步操作,不會阻塞主線程命贴。

那如何減少對代碼的入侵道宅?

// 訂閱
    [[YKDispatchCenter shared]subscribeWithBinder:self messageIdentify:@"test" handler:^(YKMessage *message, id ext) {
        NSLog(@"dd");
    }];
    
// 發(fā)送
    YKMessage *message = [[YKMessage alloc]initWithIdentify:@"test" context:@[@"1",@"2"]];
    [[YKDispatchCenter shared]dispatchMessage:message];

簡潔的API設(shè)計,簡單的使用胸蛛,是減少入侵和耦合的最好方式。

代碼

項目代碼
Demo代碼

更多的問題

然而樱报,這個消息分發(fā)中心并不完善葬项,還有不少其他問題需要考慮:

  • 如何做消息優(yōu)先級區(qū)分
  • 消息發(fā)送失敗怎么辦,是否支持重發(fā)
  • ......
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迹蛤,一起剝皮案震驚了整個濱河市民珍,隨后出現(xiàn)的幾起案子襟士,更是在濱河造成了極大的恐慌,老刑警劉巖嚷量,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陋桂,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝶溶,警方通過查閱死者的電腦和手機嗜历,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抖所,“玉大人梨州,你說我怎么就攤上這事√镌” “怎么了暴匠?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長傻粘。 經(jīng)常有香客問我每窖,道長,這世上最難降的妖魔是什么弦悉? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任窒典,我火速辦了婚禮,結(jié)果婚禮上警绩,老公的妹妹穿的比我還像新娘崇败。我一直安慰自己,他們只是感情好肩祥,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布后室。 她就那樣靜靜地躺著,像睡著了一般混狠。 火紅的嫁衣襯著肌膚如雪岸霹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天将饺,我揣著相機與錄音贡避,去河邊找鬼。 笑死予弧,一個胖子當著我的面吹牛刮吧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掖蛤,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼杀捻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚓庭?” 一聲冷哼從身側(cè)響起致讥,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤仅仆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垢袱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墓拜,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年请契,在試婚紗的時候發(fā)現(xiàn)自己被綠了咳榜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡姚糊,死狀恐怖贿衍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情救恨,我是刑警寧澤贸辈,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站肠槽,受9級特大地震影響擎淤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秸仙,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一嘴拢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寂纪,春花似錦席吴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拟杉,卻和暖如春庄涡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搬设。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工穴店, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拿穴。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓泣洞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親默色。 傳聞我的和親對象是個殘疾皇子斜棚,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)该窗,斷路器弟蚀,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,499評論 25 707
  • Android跨進程通信IPC整體內(nèi)容如下 1、Android跨進程通信IPC之1——Linux基礎(chǔ)2酗失、Andro...
    隔壁老李頭閱讀 11,815評論 11 56
  • Android跨進程通信IPC整體內(nèi)容如下 1义钉、Android跨進程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 8,236評論 1 15
  • 最近我在考慮換工作的事情规肴,一籌莫展的情況下就買了本書來開捶闸,網(wǎng)上隨便搜了一本《趨勢的力量--個人職業(yè)發(fā)展戰(zhàn)略決策必修...
    奮斗吧爸爸閱讀 423評論 0 1