iOS 多个弹窗排序处理方案:FGPopupScheduler

GitHub 地址:[FGPopupScheduler](https://github.com/FoneG/FGPopupScheduler)   
支持 cocopods,使用简便,效率不错的基础组件。

#### 前言

前些天测试反馈当新用户刚打开APP的时候,由于弹窗过多,再加上还有半透明的引导层,经常会出现多个弹窗互相覆盖,甚至阻断正常流程的情况。而需要解决这类问题,不单单要理清楚弹窗之间的依赖关系,还需要处理弹窗本身出现的条件。并且在每次有新的弹窗加入时都需要查看之前弹窗的逻辑。每一步都要耗费开发资源。

所以我们的目的就是为了解决多个弹窗,如何拆分各个弹窗间的依赖关系,并在恰当地时刻依次显示弹窗。

#### 需求分析

首先是弹窗本身的需求
* 弹窗显示
* 弹窗隐藏
* 弹窗显示需要满足的条件

然后是关于弹窗与弹窗
* 弹窗的优先级
* 弹窗是否会受到已显示弹窗的影响


弹窗显示有一个特征,就是同一个时刻只会显示一个弹窗,并且可以是一个接一个显示。如果采用采用队列来管理的话,理所当然地就需要额外处理插入、删除、清空、遍历等行为。

这一套流程下来貌似就解决了,但实际上当把所有弹窗的统一交给一个调度器来管理的话,我们必须要考虑在什么时机显示/隐藏这些弹窗才是更加合理的。


当然,[FGPopupScheduler](https://github.com/FoneG/FGPopupScheduler)   就能帮忙处理上面这些琐碎的事情,而且不止于此。

#### 实现分析


考虑到弹窗本身的多样性,甚至可能不是View。所以采用协议将弹窗的逻辑抽象处理放到`<FGPopupView>`中,只要遵守了协议将能作为就能被调度器统一管理。

@protocol FGPopupView <NSObject>

@optional
/*
 FGPopupSchedulerStrategyQueue会根据 -showPopupView: 来监听显示逻辑,如果含有动画请实现-showPopupViewWithAnimation:方法
 */
- (void)showPopupView;

/*
 FGPopupSchedulerStrategyQueue会根据 -dismissPopupView: 来监听隐藏逻辑,如果含有动画请实现-showPopupViewWithAnimation:方法
 */
- (void)dismissPopupView;

/*
 FGPopupSchedulerStrategyQueue会根据 -showPopupViewWithAnimation: 来监听显示逻辑
 */
- (void)showPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;

/*
 FGPopupSchedulerStrategyQueue会根据 -dismissPopupView: 来监听隐藏逻辑,如果含有动画请实现-dismissPopupViewWithAnimation:方法
 */
- (void)dismissPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;

/**
 FGPopupSchedulerStrategyQueue会根据-canRegisterFirstPopupView判断,当队列顺序轮到它的时候是否能够成为响应的第一个优先级PopupView。默认为YES
 */
- (BOOL)canRegisterFirstPopupViewResponder;

@end

关于弹窗显示的顺序和优先级,实际操作中还会涉及到中途插入或者移除的操作,数据结构更类似于链表,所以这里采用了C++的STL标准库:list。

具体的策略如下

typedef NS_ENUM(NSUInteger, FGPopupSchedulerStrategy) {
    FGPopupSchedulerStrategyFIFO = 1 << 0,           //先进先出
    FGPopupSchedulerStrategyLIFO = 1 << 1,           //后进先出
    FGPopupSchedulerStrategyPriority = 1 << 2        //优先级调度
};


实际上使用者还可以结合 `FGPopupSchedulerStrategyPriority | FGPopupSchedulerStrategyFIFO` 一起使用,来处理当选择优先级策略时,如何决定同一优先级弹窗的排序。


通过`hitTest`来解决弹窗显示条件的需求,如果根据当前的命中的弹窗没有通过`hitTest`,则会根据选择的调度器策略,在当前的list中获取下一个弹窗进行测试。
```

- (PopupElement *)hitTestFirstPopupResponder{
    list<PopupElement*>::iterator itor = _list.begin();
    PopupElement *element;
    do {
        PopupElement *temp = *itor;
        id<FGPopupView> data = temp.data;
        __block BOOL canRegisterFirstPopupViewResponder = YES;
        if ([data respondsToSelector:@selector(canRegisterFirstPopupViewResponder)]) {
            dispatch_sync_main_safe(^(){
                canRegisterFirstPopupViewResponder = [data canRegisterFirstPopupViewResponder];
            });
        }
        if (canRegisterFirstPopupViewResponder) {
            element = temp;
            break;
        }
        itor++;
    } while (itor!=_list.end());
    return element;
}


```


由于通过`FGPopupScheduler`来统一管理所以的弹窗,所以弹窗上面时候触发就需要组件自己来处理。这个笔者一共考虑了3个触发情况
* 添加弹窗对象的时候
* 通过Runloop监听主线程空闲的时刻
* 用户主动触发
通过上面3种情况,差不多已经能覆盖所有的使用场景。

另外,还给调度器添加了`suspended`状态,来主动挂起/恢复弹窗队列,用来控制当前调度器是否能触发`hitTest`进而展示的逻辑。

此外组件支持线程安全。考虑到操作的时机可能在任意线程,~~组件通过`pthread_mutex_t`来保证线程安全~~。值得注意的是,弹窗的显示过程会切换到主线程进行,所以不需要去额外处理了。
`pthread_mutex_t`虽然可以保证资源不会被同时占用,但必须保证上锁和解锁都在同一个线程。所以组件最后采用了信号量来替代互斥锁做线程保护。


至此,整个组件的业务是比较清晰了。FGPopupScheduler采用了状态模式,
组件需要让这三种处理方式可以自由的变动,所以采用策略模式来处理,下面是 UML 类图:

![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1562c1cc7fff4df88a5ad8e0a1a5044f~tplv-k3u1fbpfcp-zoom-1.image)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值