乐趣区

关于ios:iOS弹窗优先级调度器FGPopupScheduler

GitHub 地址:FGPopupScheduler
反对 cocopods,应用简便,效率不错的根底组件。

前言

前些天测试反馈当新用户刚关上 APP 的时候,因为弹窗过多,再加上还有半透明的疏导层,常常会呈现弹窗相互笼罩,甚至阻断失常流程的状况。而须要解决这类问题,不单单要理分明弹窗之间的依赖关系,还须要解决弹窗自身呈现的条件。并且在每次有新的弹窗退出时都须要查看之前弹窗的逻辑。每一步都要消耗开发资源。

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

需要剖析

首先是弹窗自身的需要

  • 弹窗显示
  • 弹窗暗藏
  • 弹窗显示须要满足的条件

而后是对于弹窗与弹窗

  • 弹窗的优先级
  • 弹窗是否会受到已显示弹窗的影响

弹窗显示有一个特色,就是同一个时刻只会显示一个弹窗,并且能够是一个接一个显示。如果采纳采纳队列来治理的话,天经地义地就须要额定解决插入、删除、清空、遍历等行为。

这一套流程下来貌似就解决了,但实际上当把所有弹窗的对立交给一个调度器来治理的话,咱们必须要思考在什么机会显示 / 暗藏这些弹窗才是更加正当的。

当然,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 类图:

退出移动版