iOS 彈窗順序顯示的實(shí)現(xiàn)之路

???????當(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)為主,通知為輔既峡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羡榴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子运敢,更是在濱河造成了極大的恐慌校仑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传惠,死亡現(xiàn)場離奇詭異迄沫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卦方,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門羊瘩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盼砍,你說我怎么就攤上這事困后。” “怎么了衬廷?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汽绢。 經(jīng)常有香客問我吗跋,道長,這世上最難降的妖魔是什么宁昭? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任跌宛,我火速辦了婚禮,結(jié)果婚禮上积仗,老公的妹妹穿的比我還像新娘疆拘。我一直安慰自己,他們只是感情好寂曹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布哎迄。 她就那樣靜靜地躺著,像睡著了一般隆圆。 火紅的嫁衣襯著肌膚如雪漱挚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天渺氧,我揣著相機(jī)與錄音旨涝,去河邊找鬼。 笑死侣背,一個(gè)胖子當(dāng)著我的面吹牛白华,可吹牛的內(nèi)容都是我干的慨默。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼弧腥,長吁一口氣:“原來是場噩夢啊……” “哼厦取!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸟赫,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蒜胖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抛蚤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體台谢,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年岁经,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朋沮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缀壤,死狀恐怖樊拓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塘慕,我是刑警寧澤筋夏,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站图呢,受9級特大地震影響条篷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛤织,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一赴叹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧指蚜,春花似錦乞巧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至免猾,卻和暖如春些椒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掸刊。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工免糕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓石窑,卻偏偏與公主長得像牌芋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子松逊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

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