iOS开发弹窗池(顺序显示)

需求:app启动的时候总是会显示许许多多的弹窗,那么有一个需求就是让这种弹窗一个个的显示,点掉一个显示下一个。碰到这样的需求该如何搞定呢。
解决方案:有2种实现方案,第一种是创建一个弹窗池来管控弹窗顺序,第二种是利用线程、信号量来完成顺序执行。

第一种方案:

封装GYPopupManager弹窗池,里面维护了一个弹窗的数组。

//
//  GYPopupManager.h
//  testUI
//
//  Created by gaoyu on 2021/9/8.
//

/**
 ⚠️⚠️⚠️:GYPopupManager
 此管理类不关心弹窗的show和hide
 只约束弹窗是否被拦截一个一个展示、或者某几个同时展示
 */
#import <Foundation/Foundation.h>

///优先级枚举
typedef NS_ENUM(NSUInteger, GYPopupPriority) {
    GYPopupPriorityLow = 1,     /// 低
    GYPopupPriorityMedium,      /// 中
    GYPopupPriorityHigh         /// 高
};

/// 回调
typedef void (^GYPopupBlock)(void);

// MARK: - GYPopupConfig 弹出内容配置信息
@interface GYPopupConfig : NSObject

/// 是否被拦截:默认YES
@property (nonatomic, assign) BOOL isIntercept;
/// 当前弹窗是否在展示
@property (nonatomic, assign) BOOL isShowing;
/// 弹窗优先级:默认为High(相同优先级的弹窗后加入的先展示,因为字典添加元素后默认会在第一位,所以转化为数组后也是第一个)
@property (nonatomic, assign) GYPopupPriority priority;
/// 弹窗标识:以类名为标识,便于排查
@property (nonatomic, copy, nonnull) NSString *popupClassName;
/// 展示回调
@property (nonatomic, copy, nonnull) GYPopupBlock showBlock;
/// 隐藏回调
@property (nonatomic, copy, nullable) GYPopupBlock dismissBlock;

@end




// MARK: - GYPopupManager 弹出内容管理类
@interface GYPopupManager : NSObject

/// 单例对象
+ (instancetype _Nonnull)shared;

/// 展示弹窗
/// @param popupClass 弹出内容class
/// @param showBlock 展示回调
/// @param dismissBlock 隐藏回调
- (void)popupWithClass:(_Nonnull Class)popupClass show:(_Nonnull GYPopupBlock)showBlock dismiss:(_Nullable GYPopupBlock)dismissBlock;

/// 展示弹窗
/// @param popupClass 弹出内容class
/// @param priority 优先级
/// @param showBlock 展示回调
/// @param dismissBlock 隐藏回调
- (void)popupWithClass:(_Nonnull Class)popupClass priority:(GYPopupPriority)priority show:(_Nonnull GYPopupBlock)showBlock dismiss:(_Nullable GYPopupBlock)dismissBlock;

/// 展示弹窗
/// @param popupClass 弹出内容class
/// @param priority 优先级
/// @param isIntercept 是否需要被拦截
/// @param showBlock 展示回调
/// @param dismissBlock 隐藏回调
- (void)popupWithClass:(_Nonnull Class)popupClass priority:(GYPopupPriority)priority isIntercept:(BOOL)isIntercept show:(_Nonnull GYPopupBlock)showBlock dismiss:(_Nullable GYPopupBlock)dismissBlock;

/// 隐藏弹窗
- (void)dismissPopup;

@end

.m实现

//
//  GYPopupManager.m
//  testUI
//
//  Created by gaoyu on 2021/9/8.
//

#import "GYPopupManager.h"

// MARK: - PRAlertViewConfig
@implementation GYPopupConfig

- (instancetype)init {
    self = [super init];
    if (self) {
        self.isIntercept = YES;
        self.popupClassName = @"defaultName";
        self.priority = GYPopupPriorityHigh;
    }
    return self;
}

@end




// MARK: - GYPopupManager
@interface GYPopupManager()

/// 弹出池:key:弹窗className、value:config对象
@property (nonatomic, strong) NSMutableDictionary *popupPool;
/// 当前弹窗内容
@property (nonatomic, strong) GYPopupConfig *currnetPopup;

@end

@implementation GYPopupManager

static GYPopupManager *_instance = nil;

+ (instancetype)shared {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[GYPopupManager alloc] init];
    });
    return _instance;
}

// MARK: - Public Methods
- (void)popupWithClass:(Class)popupClass show:(GYPopupBlock)showBlock dismiss:(GYPopupBlock)dismissBlock{
    [self popupWithClass:popupClass priority:GYPopupPriorityHigh isIntercept:YES show:showBlock dismiss:dismissBlock];
}

- (void)popupWithClass:(Class)popupClass priority:(GYPopupPriority)priority show:(GYPopupBlock)showBlock dismiss:(GYPopupBlock)dismissBlock {
    [self popupWithClass:popupClass priority:priority isIntercept:YES show:showBlock dismiss:dismissBlock];
}

- (void)popupWithClass:(Class)popupClass priority:(GYPopupPriority)priority isIntercept: (BOOL)isIntercept show:(GYPopupBlock)showBlock dismiss:(GYPopupBlock)dismissBlock {
    GYPopupConfig *config = [[GYPopupConfig alloc] init];
    config.popupClassName = NSStringFromClass(popupClass);
    config.priority = priority;
    config.isIntercept = isIntercept;
    [self popupWithConfig:config show:showBlock dismiss:dismissBlock];
    
}

// MARK: - Private Methods
- (void)popupWithConfig:(GYPopupConfig *)config show:(GYPopupBlock)showBlock dismiss:(GYPopupBlock)dismissBlock {
    config.showBlock = showBlock;
    config.dismissBlock = dismissBlock;
    config.isShowing = YES;
    
    // 加入弹出池,同一个弹窗避免重复添加
    [self.popupPool setObject:config forKey:config.popupClassName];
    
    // 当前有弹窗在显示:self.popupPool.allKeys.count > 1
    // 当前弹窗被拦截:isIntercept == YES
    if (config.isIntercept && self.popupPool.allKeys.count > 1) {
        config.isShowing = NO;
        return;
    }
    
    self.currnetPopup = config;
    
    //回主线程
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        showBlock ? showBlock() : nil;
    });
}


/// 清除弹出内容
- (void)dismissPopup {
    // 获取到当前弹出内容并删除
    [self deletePopupWithConfig:self.currnetPopup];
    
    // 是否有弹窗在显示 是:拦截其他未显示弹窗;否:查看是否有下一个可显示弹窗并显示
    if ([self isShowingSomeAlert]) {
        return;
    } else {
        // 优先级排序
        NSArray * values = [self.popupPool allValues];
        values = [self sortByPriority:values];
        
        // 当前没有正在展示的弹窗,则展示被拦截的弹窗
        if (values.count > 0) {
            // 查询是否有可以展示的弹窗:条件:1.已加入缓存、2.被拦截 3、实现了展示回调
            // 优先级 1 > 2 > 3
            // 找到一个立即展示,并退出循环(相同优先级的弹窗后加入的先展示,因为字典添加元素后默认会在第一位,所以转化为数组后也是第一个)
            for (GYPopupConfig *config in values) {
                GYPopupBlock showBlock = config.showBlock;
                if (config.isIntercept && showBlock) {
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        config.isShowing = YES;
                        self.currnetPopup = config;
                        showBlock();
                    });
                    break;
                }
            }
        }
    }
}

/// 是否正在展示某个弹窗
- (BOOL)isShowingSomeAlert{
    __block BOOL isShowSomeAlert = NO;
    [self.popupPool enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        GYPopupConfig *config = obj;
        if (config.isShowing) {
            isShowSomeAlert = YES;
            *stop = YES;
        }
    }];
    return isShowSomeAlert;
}

/// 根据优先级排序
/// @param configArray 弹窗配置数组
- (NSArray *)sortByPriority:(NSArray *)configArray {
    NSComparator comparator = ^(GYPopupConfig *obj1, GYPopupConfig *obj2) {
        if (obj1.priority > obj2.priority) {
            return NSOrderedAscending;
        }
        if (obj1.priority < obj2.priority) {
            return NSOrderedDescending;
        }
        return NSOrderedSame;
    };
    return [configArray sortedArrayUsingComparator:comparator];
}

/// 根据配置删除指定弹出内容
/// @param config 弹出配置
- (void)deletePopupWithConfig:(GYPopupConfig *)config {
    if (config == nil) return;
    GYPopupBlock dismissBlock = config.dismissBlock;
    dismissBlock ? dismissBlock() : nil;
    if ([self.popupPool.allKeys containsObject:config.popupClassName]) {
        [self.popupPool removeObjectForKey:config.popupClassName];
    }
    self.currnetPopup = nil;
}
 
// MARK: - Getters
- (NSMutableDictionary *)popupPool {
    if (!_popupPool) {
        _popupPool = [[NSMutableDictionary alloc] init];
    }
    return _popupPool;
}

@end


把弹窗全部加到封装好的GYPopupManager弹窗池中

[[GYPopupManager shared] popupWithClass:[Toast class] priority:GYPopupPriorityMedium show:^{
            // 弹窗的展示
            [[Toast shared] show];
} dismiss:^{

}];

// 弹窗消失
[[GYPopupManager shared] dismissPopup];
第二种方案:

通过子线程+信号量去阻塞后续弹窗的展示,来完成顺序弹窗

思路:
创建全局只容纳1个单位的信号量
Show的时候 Lock
Dismiss的时候 Release Lock

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BaseAlertView : UIView

- (void)show;

- (void)dismiss;

@end

NS_ASSUME_NONNULL_END

.m实现

#import "BaseAlertView.h"

//全局信号量
dispatch_semaphore_t _globalInstancesLock;
//执行QUEUE的Name
char *QUEUE_NAME = "com.alert.queue";

//初始化 -- 借鉴YYWebImage的写法
static void _AlertViewInitGlobal() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _globalInstancesLock = dispatch_semaphore_create(1);
    });
}

@implementation BaseAlertView
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:[UIScreen mainScreen].bounds];
    if (self)
    {
        _AlertViewInitGlobal();
    }
    return self;
}

#pragma mark - public
- (void)show
{
    //位于非主线程 不阻塞
    dispatch_async(dispatch_queue_create(QUEUE_NAME, DISPATCH_QUEUE_SERIAL), ^{
        //Lock
        dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
        //保证主线程UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication].windows.firstObject addSubview:(UIView *)self];
        });
    });
}

- (void)dismiss
{
    dispatch_async(dispatch_queue_create(QUEUE_NAME, DISPATCH_QUEUE_SERIAL), ^{
        //Release Lock
        dispatch_semaphore_signal(_globalInstancesLock);

        dispatch_async(dispatch_get_main_queue(), ^{
            [self removeFromSuperview];
        });
    });
}

@end

实现方法:弹窗的view继承于BaseAlertView,在展示的时候调用父类的show方法即可。

参考文档:
iOS弹窗顺序弹出管理
iOS 弹窗顺序显示 -- 信号量实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容