美文网首页iOS经验总结
iOS开发弹窗顺序显示

iOS开发弹窗顺序显示

作者: 铁头娃_e245 | 来源:发表于2021-09-24 16:58 被阅读0次

    需求: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 弹窗顺序显示 -- 信号量实践

    相关文章

      网友评论

        本文标题:iOS开发弹窗顺序显示

        本文链接:https://www.haomeiwen.com/subject/mwfzgltx.html