前言
隨著項(xiàng)目的業(yè)務(wù)越來越重,客戶端與服務(wù)端對(duì)實(shí)時(shí)性要求較高的數(shù)據(jù)交互,不能再依賴HTTP輪詢或者可能會(huì)造成數(shù)據(jù)丟失的APNS推送。例如,訂單狀態(tài)的改變、新的運(yùn)營(yíng)消息、同時(shí)存在兩類客戶端(比如餓了么平臺(tái)的送餐員與用戶)需要進(jìn)行頻繁數(shù)據(jù)交互。基于上述原因,項(xiàng)目引入socket通信。通道建立好之后,就將之前的輪詢機(jī)制、依賴APNS推送的部分、還有新需求,移植到sockct通道。
關(guān)于socket的搭建以及服務(wù)端與客戶端的協(xié)議制定,有時(shí)間我單開一篇與大家討論~~
客戶端與服務(wù)端的通道建立好之后所面臨的問題。由于業(yè)務(wù)比較繁重,通道過來的(socket傳遞過來的數(shù)據(jù),以下簡(jiǎn)稱消息)消息,又有好多類型,每種類型又回包含不同的Value,如何讓業(yè)務(wù)層不同的頁面、組件、服務(wù)等準(zhǔn)確的接收到需要的消息,是本篇的重點(diǎn)。
討論
- 首先,消息中心如何派發(fā)消息隊(duì)列里的消息到最終的接收者。
-
依賴廣播
廣播可以讓每一個(gè)監(jiān)聽廣播的接收者都能接收到消息。但是存在一個(gè)互斥的問題,就是,如果想要特定的接收者,接收特定的消息,就得增加特定的通知類型,類型越多,維護(hù)成本越大,且需要消息服務(wù)增加大量的判斷。如果不想增加太多的類型,比如關(guān)于訂單的所有消息,都叫做xxxxxxxOrderNotification,這樣一來,類型少了,但是到了業(yè)務(wù)層,但凡是監(jiān)聽了訂單類型消息的接收者,都得加入“大同小異”的判斷,也是相當(dāng)麻煩的操作。 -
KVO
如果使用KVO,這就需要消息中心總是根據(jù)業(yè)務(wù)不同,頻繁的變動(dòng)消息的Model且KVO本身就不是太好用的一種機(jī)制。 -
代理
如何使用代理?怎樣在delegate回調(diào)處減少大量的判斷、保持更好的擴(kuò)展性、業(yè)務(wù)層代碼更簡(jiǎn)潔?
-
依賴廣播
- 如何處理多個(gè)接收者對(duì)于某條消息的相應(yīng)優(yōu)先級(jí)。
構(gòu)建過程

從消息接收者的角度出發(fā)考慮問題
比如一個(gè)訂單服務(wù)中頁面,根據(jù)當(dāng)前訂單ID,只想接收關(guān)于當(dāng)前訂單ID的消息,比如消息結(jié)構(gòu)為:
{
"msg_body" : {
"timestamp" : 172831819,
"type" : "order_status_change",
"title" : "",
"sub_title" : "",
"content" : "",
"fmt_date" : "07-22 18:22",
"extra" : {
"order_no": "TAINIUBILE1234"
}
},
"msg_id" : "00544"
}
如何在訂單服務(wù)中頁面,根據(jù)order_no訂閱該條消息?KVC
首先創(chuàng)建消息Model和消息服務(wù)
@interface YGMessage : YGSBaseModel
@property (nonatomic, assign, getter=isProcessed) BOOL processed; //已被處理過
@property (nonatomic, strong) NSDictionary *msg_body;
@property (nonatomic, copy) NSString *msg_id;
@end
@interface YGMessageCenter : NSObject
+ (instancetype)sharedInstance;
@end
關(guān)于YGMessage的屬性processed的設(shè)計(jì),本人的思路是,有可能同一條消息,被多個(gè)訂閱者所接收,由于消息服務(wù)無法判斷業(yè)務(wù)層的邏輯,比如,訂單詳情頁面處理過的訂單更新的消息,上層接收者可以不做處理等邏輯,可以通過將消息的processed改為YES,去跳過重復(fù)處理,當(dāng)然可以更好的方式去防止重復(fù)處理,
整個(gè)的消息體,可以在消息中心解析為一個(gè)字典,通過[dict valueForKey:@"msg_body.extra.order_no"]就可以拿到order_no,而且根據(jù)value=TAINIUBILE1234派發(fā)到訂單服務(wù)中頁面,由此就可以構(gòu)建一個(gè)消息訂閱條件
typedef struct YGMessageCondition {
NSString * _Nullable keyPath; //
_Nullable id value;
} YGMessageCondition;
//提供一個(gè)構(gòu)造函數(shù)
static inline YGMessageCondition YGMessageConditionMake(NSString * _Nullable keyPath, _Nullable id value) {
YGMessageCondition condition;
condition.keyPath = keyPath;
condition.value = value;
return condition;
}
消息服務(wù)派發(fā)消息時(shí)通過代理
生成代理協(xié)議
@protocol YGMessageCenterDelegate <NSObject>
@optional;
/**
派發(fā)消息
@param message 消息
@param intercept 是否阻斷消息繼續(xù)發(fā)給其他訂閱者
*/
- (void)messageCenterDidReceiveMessage:(YGMessage *)message intercept:(BOOL *)intercept;
@end
每次訂閱消息的時(shí)候,塞一個(gè)Condition給消息服務(wù),YGMessageCenter添加方法:
/**
添加代理
@param delegate 代理
所有接收到的消息都會(huì)傳給該delegate(如果消息未被棧頂delegate攔截)
*/
- (void)addDelegate:(id<YGMessageCenterDelegate>)delegate;
/**
根據(jù)YGMessageCondition添加代理
@param delegate 代理
@param condition keyPath-value
如果condition.keyPath = nil,則所有接收到的消息都會(huì)傳給該delegate(如果消息未被棧頂delegate攔截)
如果condition.value = nil,則所有接收到的msg.keyPath下有值的消息,都會(huì)傳給該delegate(如果消息未被棧頂delegate攔截)
*/
- (void)addDelegate:(id<YGMessageCenterDelegate>)delegate condition:(YGMessageCondition)condition;
既然有訂閱方法,必然就需要移除訂閱的方法
/**
移除代理
@param delegate 代理
*/
- (void)removeDelegate:(id<YGMessageCenterDelegate>)delegate;
/**
根據(jù)YGMessageCondition移除代理
@param delegate 代理
@param condition keyPath-value
如果condition.keyPath = nil,則移除該代理,否則,移除指定的keyPath監(jiān)聽
如果condition.value = nil,則移除keyPath的所有監(jiān)聽
*/
- (void)removeDelegate:(id<YGMessageCenterDelegate>)delegate condition:(YGMessageCondition)condition;
再看
addDelegate:與addDelegate:condition:方法的內(nèi)部實(shí)現(xiàn)
首先思考一個(gè)問題,添加的消息的代理肯定是多個(gè),所以服務(wù)內(nèi)部必然有一個(gè)集合去保留這些代理,但是如果直接將代理對(duì)象加入到某個(gè)集合當(dāng)中,很容易會(huì)因?yàn)闃I(yè)務(wù)層童鞋的操作不當(dāng),造成強(qiáng)引用問題?;谶@個(gè)原因我們創(chuàng)建一個(gè)中間件,來弱引用傳入的代理,并保存訂閱條件等其他一些操作,此處命名為YGMessageMonitor消息監(jiān)聽者。
YGMessageMonitor該類設(shè)計(jì)的屬性及方法,下面會(huì)根據(jù)構(gòu)建過程逐步解釋
@interface YGMessageMonitor : NSObject
@property (nonatomic, weak) id<YGMessageCenterDelegate> delegate; //代理
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray *> *keyPathValueMaps;
@property (nonatomic, assign) BOOL alwaysRespond; //總是響應(yīng)所有消息
@property (nonatomic, assign, readonly) BOOL isInvalid; //是否無效,業(yè)務(wù)類型為0或者代理為空
- (void)addCondition:(YGMessageCondition)condition;
- (void)removeCondition:(YGMessageCondition)condition;
- (BOOL)respondsToMessage:(YGMessage *)obj;
@end
addDelegate:方法的實(shí)現(xiàn),當(dāng)某個(gè)代理對(duì)象,不攜帶任何condition加入到消息服務(wù)中時(shí)
- 判空(無須解釋)
- 獲取弱引用該delegate對(duì)象的中間件
YGMessageCenter添加
@property (nonatomic, strong) NSMutableArray *monitorArray;
- (YGMessageMonitor *)monitorForDelegate:(id<YGMessageCenterDelegate>)delegate {
NSMutableArray *invalidArray = [NSMutableArray array];
YGMessageMonitor *monitor = nil;
for (YGMessageMonitor *obj in self.monitorArray) {
if (obj.delegate == delegate) {
monitor = obj;
}
if (obj.delegate == nil) {
[invalidArray addObject:obj];
}
}
[self.monitorArray removeObjectsInArray:invalidArray];
return monitor;
}
- 若獲取到的monitor對(duì)象為空時(shí),創(chuàng)建新的monitor,并若引用delegate
- 設(shè)置該monitor的
alwaysRespond屬性為YES,該屬性的定義為,無條件響應(yīng)任何消息,增加該屬性的目的是減少判斷,提高效率
- (void)addDelegate:(id<YGMessageCenterDelegate>)delegate {
if (delegate == nil) { return ; }
//創(chuàng)建一個(gè)消息監(jiān)聽者,無條件的delegate,設(shè)置其監(jiān)聽者alwaysRespond為yes
YGMessageMonitor *monitor = [self monitorForDelegate:delegate];
if (monitor == nil) {
monitor = [[YGMessageMonitor alloc] init];
monitor.delegate = delegate;
[self.monitorArray addObject:monitor];
}else {
//如果存在監(jiān)聽條件,則移除所有的監(jiān)聽條件,釋放內(nèi)存
[monitor.keyPathValueMaps removeAllObjects];
}
monitor.alwaysRespond = YES;
}
addDelegate:condition:方法的實(shí)現(xiàn),當(dāng)某個(gè)代理對(duì)象攜帶condition加入到消息服務(wù)中時(shí)
- 判空(無須解釋)
- 判斷
condition.keyPath是否為空,若為空調(diào)用addDelegate:方法 - 獲取弱引用該delegate對(duì)象的中間件
- 若獲取到的monitor對(duì)象為空時(shí),創(chuàng)建新的monitor,并若引用delegate
- 判斷moitor的alwaysRespond是否為YES,
if (monitor.alwaysRespond) { return ;} - 若alwaysRespond為NO,則調(diào)用
[monitor addCondition:condition];
- (void)addDelegate:(id<YGMessageCenterDelegate>)delegate condition:(YGMessageCondition)condition {
if (delegate == nil) { return ; }
//若condition.keypath為空,則設(shè)置該delegate為無條件
if (condition.keyPath.length == 0) {
[self addDelegate:delegate];
return ;
}
YGMessageMonitor *monitor = [self monitorForDelegate:delegate];
if (monitor == nil) {
monitor = [[YGMessageMonitor alloc] init];
monitor.delegate = delegate;
[self.monitorArray addObject:monitor];
}
//若該代理已被設(shè)置為無條件監(jiān)聽者,則不再處理監(jiān)聽條件
if (monitor.alwaysRespond) { return ;}
[monitor addCondition:condition];
}
接著看YGMessageMonitor addCondition:方法如何實(shí)現(xiàn),考慮以下幾種情況
- 若傳入的condition.value為nil時(shí),也就表示該消息訂閱者不必匹配keyPath*下具體的值,只要能匹配到存在值,就接收消息派發(fā)
- 若傳入的condition.value不為 nil 時(shí),則表示該消息訂閱者匹配keyPath下具體的值
- 若同一個(gè)消息訂閱者,多次傳入condition且擁有不同的keyPath,或者相同的keyPath匹配不同的value
由于消息訂閱者與YGMessageMonitor是一對(duì)一的關(guān)系,所以需要給YGMessageMonitor增加屬性
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray *> *keyPathValueMaps;
在調(diào)用addCondition:方法時(shí)首先需要獲取到condition.keyPath對(duì)應(yīng)的value數(shù)組
- (NSMutableArray *)valueArrayForKeyPath:(NSString *)keyPath {
NSMutableArray *array = self.keyPathValueMaps[keyPath];
if (array == nil) {
array = [NSMutableArray array];
self.keyPathValueMaps[keyPath] = array;
}
return array;
}
其次判斷condition.value是否為nil,若為nil則需要有一個(gè)特殊的value來標(biāo)記訂閱者接收該keyPath下所有的存在值的消息
定義常量static NSString * const YGAlwaysRespondValue = @"alwaysRespondValue";,賦值并保存value
- (void)addCondition:(YGMessageCondition)condition {
//查找監(jiān)聽條件keypath下對(duì)照的value數(shù)組
NSMutableArray *array = [self valueArrayForKeyPath:condition.keyPath];
//若value為空,則監(jiān)聽該keypath下存在值的所有消息
if (condition.value == nil) {
condition.value = YGAlwaysRespondValue;
}
[array addObject:condition.value];
}
另外增加removeCondition:方法
- (void)removeCondition:(YGMessageCondition)condition {
NSMutableArray *array = self.keyPathValueMaps[condition.keyPath];
if (array == nil) { return ;}
if (condition.value == nil) {
condition.value = YGAlwaysRespondValue;
}
[array removeObject:condition.value];
if (array.count == 0) {
[self.keyPathValueMaps removeObjectForKey:condition.keyPath];
}
}
至此消息訂閱已基本實(shí)現(xiàn),下面看消息派發(fā),當(dāng)接收到socket傳遞過來的數(shù)據(jù)時(shí)
{
"msg_body" : {
"timestamp" : 172831819,
"type" : "order_status_change",
"title" : "",
"sub_title" : "",
"content" : "",
"fmt_date" : "07-22 18:22",
"extra" : {
"order_no": "TAINIUBILE1234"
}
},
"msg_id" : "00544"
}
首先解析成YGMessageModel,在解析model之前,可以做排重工作,這部分在源碼中給出,再次不做講解,然后獲取monitorArray中所有響應(yīng)該消息的訂閱者,調(diào)用messageCenterDidReceiveMessage:進(jìn)行派發(fā)
- 先看如何判斷monitor是否響應(yīng)該消息
給YGMessageMonitor增加方法respondsToMessage:,該方法只是簡(jiǎn)單的實(shí)現(xiàn)了部分判斷條件,可根據(jù)不同業(yè)務(wù)情況做擴(kuò)展,不多做講解
- (BOOL)respondsToMessage:(YGMessage *)message {
if (self.alwaysRespond) { return YES; }
NSArray *keyPathArray = self.keyPathValueMaps.allKeys;
for (NSString *keyPath in keyPathArray) {
id value = [message valueForKeyPath:keyPath];
if (value == nil) {
return NO;
}
NSArray *valueArray = self.keyPathValueMaps[keyPath];
for (id conditionValue in valueArray) {
if (conditionValue == YGAlwaysRespondValue) {
return YES;
}
if ([value isKindOfClass:[NSString class]] && [conditionValue isKindOfClass:[NSString class]]) {
if ([value isEqualToString:conditionValue]) {
return YES;
}
}
if ([value isKindOfClass:[NSNumber class]] && [conditionValue isKindOfClass:[NSNumber class]]) {
if ([value isEqualToNumber:conditionValue]) {
return YES;
}
}
}
}
return NO;
}
- 在判斷YGMessageMonitor是否響應(yīng)某條消息的時(shí)候,同時(shí)判斷該監(jiān)聽者是否已經(jīng)為無效監(jiān)聽著,YGMessageMonitor重寫isInvalid的
getter方法
- (BOOL)isInvalid {
if (self.delegate == nil) {
return YES;
}
if (self.alwaysRespond) {
return NO;
}
return self.keyPathValueMaps.allKeys.count == 0;
}
- 最后派發(fā)完消息,移除monitorArray所有無效的monitor
- (void)postMessage:(YGMessage *)message {
for (YGMessageMonitor *monitor in self.monitorArray.reverseObjectEnumerator) {
if (monitor.isInvalid) {
[self.invalidMonitorArray addObject:monitor];
continue ;
}
BOOL responds = [monitor respondsToMessage:message];
if (!responds) { continue ; }
if ([monitor.delegate respondsToSelector:@selector(messageCenterDidReceiveMessage:intercept:)]) {
BOOL intercept = NO;
[monitor.delegate messageCenterDidReceiveMessage:message intercept:&intercept];
if (intercept) { break ; }
}
}
[self.monitorArray removeObjectsInArray:self.invalidMonitorArray];
[self.invalidMonitorArray removeAllObjects];
}
至此,整個(gè)的消息訂閱與派發(fā)功能基本實(shí)現(xiàn),一些完善的邏輯,根據(jù)自身業(yè)務(wù)做不同的處理,在此就不做贅述。若有不明之處,可以評(píng)論溝通。不完善之處,還請(qǐng)慷慨指出。