???????當(dāng)產(chǎn)品搞了一堆彈窗的需要后闲礼,如各自提示牍汹、引導(dǎo)铐维,然后發(fā)現(xiàn)這樣不好,需要進(jìn)行排序顯示慎菲,也就是顯示一個(gè)之后再顯示下一個(gè)嫁蛇,這事就落到開發(fā)手上。這些彈窗有各種不同類型露该,并且每種彈窗可能有取消(好的)睬棚,或者還有確定(來進(jìn)入下一彈窗),也就是說整個(gè)業(yè)務(wù)完成之后才算消失解幼。
???????每個(gè)彈窗顯示的內(nèi)容是根據(jù)服務(wù)端下發(fā)的數(shù)據(jù)來配置抑党。下發(fā)的方式有 HTTP (一次性下發(fā)所有需要顯示的彈窗數(shù)據(jù))或 Socket (分開下發(fā)各個(gè)彈窗數(shù)據(jù),也就是說下發(fā)的先后順序可能不同)撵摆。
???????場景描述完了底靠,用我的真實(shí)需求來說明:彈窗分別有心愿審核結(jié)果(A)、心愿填寫(B)特铝、心愿分享引導(dǎo)(C)暑中。A有兩種狀態(tài),審核通過(A1鲫剿,只有確定鳄逾,點(diǎn)擊則消失)和審核拒絕(A2,有取消和確定牵素,取消點(diǎn)擊則消失严衬,確定則進(jìn)入下個(gè)流程),在 A2 時(shí)點(diǎn)擊確定則顯示 B笆呆,然后可以進(jìn)行心愿的修改请琳,也可以取消來結(jié)束,當(dāng)上面流程結(jié)束后顯示 C赠幕,以上都需要先判斷是否有 A俄精、B、C 可供顯示榕堰,有就顯示竖慧,無則跳過。下面是簡單的順序圖逆屡,任意節(jié)點(diǎn)沒有則跳過圾旨,直接到下一個(gè)。
A
/ \
A1 A2
\ \
C B
/
C
接著聊聊排序顯示的實(shí)現(xiàn):
一魏蔗、
???????最初的解決方案是想簡單暴力點(diǎn)砍的,因?yàn)榧?xì)看上面的需求,其實(shí)就是兩層莺治。要記錄的就是 A 是否出現(xiàn)廓鞠,是否點(diǎn)擊顯示 B帚稠,然后是否顯示顯示 C 。前面說的如果是 Socket 下發(fā) A 和 C 時(shí)床佳,暫不考慮 C 在 A 前面的情況(其實(shí)后續(xù)實(shí)現(xiàn) FIFO 就可以不理會(huì))滋早,因?yàn)槟壳靶枨笫?A 肯定在 C 前面。
???????通過 bShowWishResult 記錄是否顯示 A砌们,而接著是顯示 B 則等 B 消失時(shí)把 bShowWishResult = NO杆麸,不然則 A 消失時(shí)置 NO,此時(shí)再通過 shareGuideDic 判斷是否顯示 C浪感。當(dāng) A 和 C 同時(shí)下發(fā)角溃,C 先判斷 bShowWishResult,為 NO 才顯示篮撑,不然先記錄 shareGuideDic 再等待。代碼實(shí)現(xiàn)如下:
// 是否顯示心愿結(jié)果 A 中
@property (nonatomic) BOOL bShowWishResult;
// 心愿分享引導(dǎo) C 的數(shù)據(jù)
@property (nonatomic, strong) NSDictionary *shareGuideDic;
- (void)doConnectSocket:(NSDictionary *)dic {
if (type == A) {
self.bShowWishResult = YES;
[self p_showWishResultView:dic];
}
else if (type == C) {
if (self.bShowWishResult == YES) {
self.shareGuideDic = dic;
}
else {
[self p_showShareGuideView:dic];
}
}
}
// 顯示 A 頁面
- (void)p_showWishResultView:(NSDictionary *)dic {
...
// 顯示完消失匆瓜,通過 delegate 或者 block 來調(diào)用 p_nextView 顯示 C
self.bShowWishResult = NO;
[self p_nextView];
}
// 顯示 C 頁面
- (void)p_showShareGuideView:(NSDictionary *)dic {
// 顯示完消失赢笨,通過 delegate 或者 block 來調(diào)用,來清空記錄
self.shareGuideDic = nil;
}
- (void)p_nextView {
// 有 C 的數(shù)據(jù)則顯示 C 頁面
if (self.shareGuideDic != nil) {
[self p_showShareGuideView:self.shareGuideDic];
}
}
??????不論是 A 之后直接到 C驮吱, 還是 A 到 B茧妒,再到 C,都得通過 delegate 或者 block 到主 VC 來調(diào)用 [self p_nextView]; 來顯示下個(gè)彈窗左冬。這里有個(gè)問題是每個(gè)頁面的都需要這種消失的回調(diào)桐筏。而類似 A1,本來就是個(gè)簡單的提醒彈窗拇砰,此時(shí)就不得不也添加回調(diào)梅忌。這樣等于增加了總的代碼量,主 VC 也增加處理除破。所以后面會(huì)討論是否使用 監(jiān)聽 來減低這種耦合牧氮。
二、
???????當(dāng)完成第一版的時(shí)候瑰枫,已經(jīng)滿足了需求踱葛。可是再細(xì)想下光坝,會(huì)發(fā)現(xiàn)其實(shí)這樣做并不通用尸诽,也就是說再增加一個(gè)類似A或者C的彈窗時(shí)候,需要再增加 property 的字典等邏輯盯另。而這種排序顯示讓人想到了堆(heap)性含,F(xiàn)IFO。所以使用堆的數(shù)據(jù)結(jié)構(gòu)來保存這種順序的話土铺,然后再拿出來顯示胶滋。
???????需要增加:項(xiàng)目中 Socket 過來的數(shù)據(jù)是要判斷同一場次的板鬓,就是推 type 為 A 的gameId 需要和 type 為 B 的 gameId 要一致。gameId 是在進(jìn)場的 HTTP 或者初始的 Socket 路由得到的究恤,我們額外保存當(dāng)前 gameId 就可以俭令。
???????現(xiàn)在我們使用 heap 管理類,將同個(gè) gameId 的彈窗數(shù)據(jù)一一添加到對應(yīng) key-value 數(shù)組中部宿。這樣就不用因?yàn)槎鄠€(gè)彈窗再添加類似第一版的 bShowWishResult 和 shareGuideDic抄腔,現(xiàn)在直接通過 QHHeapManager 的 queueDicValue 來管理。代碼如下:
#define QHHeapTypeKey @"type"
#define QHHeapDicKey @"dic"
typedef enum : NSUInteger {
QHHeapTypeWish,
QHHeapTypeShare
} QHHeapType;
@interface QHHeapManager()
@property (nonatomic, strong) NSMutableDictionary *queueDicValue;
@end
@implementation QHHeapManager
- (NSArray *)hasNext:(NSSting *)gameId {
NSArray *value = self.queueDicValue[gameId];
if (value != nil && value.count > 0) {
return [value copy];
}
return nil;
}
- (void)addQueue:(NSSting *)gameId value:(nonnull id)data {
NSArray *value = self.queueDicValue[gameId];
if (value != nil) {
NSMutableArray *newValue = [NSMutableArray arrayWithArray:value];
[newValue addObject:[data copy]];
[self.queueDicValue setObject:newValue forKey:gameId];
}
else {
[self.queueDicValue setObject:@[[data copy]] forKey:gameId];
}
}
- (void)goNext:(NSSting *)gameId {
NSArray *value = self.queueDicValue[gameId];
if (value != nil && value.count > 0) {
NSMutableArray *newValue = [NSMutableArray arrayWithArray:value];
[newValue removeObjectAtIndex:0];
[self.queueDicValue setObject:newValue forKey:gameId];
if (newValue.count > 0) {
// 下一個(gè)
[self.delegate nextQueue:[newValue firstObject]];
}
}
}
@end
調(diào)用方式理张,及回調(diào)邏輯代碼:
// A
[self.heapManager addQueue:@"123" value:@{QHHeapTypeKey: @QHHeapTypeWish, QHHeapDicKey: dic}];
// B
[self.heapManager addQueue:@"123" value:@{QHHeapTypeKey: @QHHeapTypeShare, QHHeapDicKey: dic}];
// 完成 A 之后赫蛇,調(diào)用
[self.heapManager goNext:@"123"];
// delegate
- (void)nextQueue:(id)value {
NSDictionary *dic = ((NSDictionary *) alue);
if ([dic[QHHeapTypeKey] integerValue] == QHHeapTypeShare) {
[self p_showShareGuideView:dic[QHHeapDicKey]];
}
}
三、
???????說到這里可以看出 QHHeapManager 的 API 只支持用于 gameId雾叭,或者類似 gameId 的 key悟耘。但是注意他們不能一起使用,除非使用特殊字符區(qū)分织狐,必須唯一暂幼。而 delegate 的 - (void)nextQueue:(id)value; 也需要區(qū)分大類,再區(qū)分小類移迫。因此需要修改上面的 delegate 的API旺嬉。最終我通過增加枚舉來維護(hù)不同的字段,這樣也方便查看目前使用的排序種類厨埋,避免重復(fù)邪媳。而下面的完整版也增加了上面提到的 NSNotificationCenter 來解耦,讓 A1 無須多寫 delegate荡陷,只需在頁面消失添加 Notif 的 post雨效。代碼如下:
[[NSNotificationCenter defaultCenter] postNotificationName:GONEXT_LUACYQUEUE_NOTIFICATIONNAME object:@(QHQueueTypeEnter)];
QHHeapManager 的完整代碼如下:
// 通知
#define GONEXT_LUACYQUEUE_NOTIFICATIONNAME @"GONEXTLUACYQUEUENOTIFICATIONNAME"
// 增加大類
typedef enum : NSUInteger {
QHQueueTypeFinal,
QHQueueTypeEnter
} QHQueueType;
typedef enum : NSUInteger {
QHHeapTypeWish,
QHHeapTypeShare
} QHHeapType;
#define QHHeapTypeKey @"type"
#define QHHeapDicKey @"dic"
// 大類的 key
#define QHHeapOtherKey @"otherKey"
#define QHQueueValue @"queueValue"
@interface QHHeapManager()
@property (nonatomic, strong) NSMutableDictionary *queueDicValue;
@end
@implementation QHHeapManager
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:GONEXT_LUACYQUEUE_NOTIFICATIONNAME object:nil];
_queueDicValue = nil;
}
- (instancetype)init {
self = [super init];
if (self) {
[self p_setup];
}
return self;
}
- (void)p_setup {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(goLuckyQueueNextAction:) name:GONEXT_LUACYQUEUE_NOTIFICATIONNAME object:nil];
}
- (NSArray *)nextValue:(QHQueueType)type {
NSArray *value = self.queueDicValue[@(type)];
if (value != nil && value.count > 0) {
return [value copy];
}
return nil;
}
- (void)deleteValue:(QHQueueType)type {
[self.queueDicValue removeObjectForKey:@(type)];
}
- (void)addQueue:(QHQueueType)type value:(nonnull id)data {
NSArray *value = self.queueDicValue[@(type)];
if (value != nil) {
NSMutableArray *newValue = [NSMutableArray arrayWithArray:value];
[newValue addObject:[data copy]];
[self.queueDicValue setObject:newValue forKey:@(type)];
}
else {
[self.queueDicValue setObject:@[[data copy]] forKey:@(type)];
}
}
- (void)goNext:(QHQueueType)type dicComplete:(BOOL(^)(QHQueueType type, id value))complete {
if (type == QHQueueTypeFinal ||
type == QHQueueTypeEnter) {
NSArray *value = self.queueDicValue[@(type)];
if (value != nil && value.count > 0) {
NSMutableArray *newValue = [NSMutableArray arrayWithArray:value];
[newValue removeObjectAtIndex:0];
[self.queueDicValue setObject:newValue forKey:@(type)];
if (newValue.count > 0) {
if (complete == nil || complete(type, [newValue firstObject]) == YES) {
if (newValue.count > 0) {
[self.delegate nextQueue:type value:[newValue firstObject]];
}
}
}
}
}
}
- (void)goLuckyQueueNextAction:(NSNotification *)notif {
id type = notif.object;
if (type != nil) {
[self goNext:[type integerValue] dicComplete:nil];
}
}
- (NSMutableDictionary *)queueDicValue {
if (_queueDicValue == nil) {
_queueDicValue = [NSMutableDictionary new];
}
return _queueDicValue;
}
@end
#pragma mark - QHHeapManagerDelegate
- (void)nextQueue:(QHQueueType)type value:(id)value {
if (type == QHQueueTypeFinal) {
NSDictionary *dicValue = value;
if ([dicValue[QHHeapTypeKey] integerValue] == QHHeapTypeShare) {
[self p_showFinalShareGuideView];
}
}
else if (type == QHQueueTypeEnter) {
NSDictionary *dicValue = value;
if ([dicValue[QHHeapTypeKey] integerValue] == QHHeapTypeWish) {
[self p_wishAction];
}
}
}
???????注意上面有個(gè) goNext 函數(shù)的增加了 block,用于對已保存的字典數(shù)據(jù)進(jìn)行再比較废赞,來判斷是否繼續(xù) goNext设易。如下:
- (void)goNext:(QHQueueType)type dicComplete:(BOOL(^)(QHQueueType type, id value))complete;
總結(jié):
???????目前的解決方案是使用字典,大類作為key蛹头,內(nèi)容及小類使用數(shù)組顿肺。其實(shí)就是利用數(shù)組的堆結(jié)構(gòu),add 和 firstObject渣蜗,從而達(dá)到 FIFO屠尊,順序獲取需要顯示的彈窗類型及數(shù)據(jù)等。而增加的通知耕拷,它雖然能減耦讼昆,但是當(dāng)多個(gè)類都創(chuàng)建 QHHeapManager 時(shí),都會(huì)監(jiān)聽這個(gè)通知骚烧,而通過類型判斷可以避免觸發(fā)對應(yīng)邏輯(這也是為什么每個(gè)順序都得添加一個(gè)大類的枚舉)浸赫,但是這種通知是消耗資源的闰围。所以頁面的邏輯還是得以回調(diào)為主,通知為輔既峡。