iOS popup priority scheduler: FGPopupScheduler

GitHub address: FGPopupScheduler
supports cocopods, a basic component that is easy to use and efficient.

foreword

A few days ago, the test feedback was that when a new user first opened the APP, due to too many pop-up windows and a translucent guide layer, the pop-up windows often covered each other and even blocked the normal process. To solve such problems, it is not only necessary to clarify the dependencies between pop-up windows, but also to deal with the conditions under which the pop-up windows themselves appear. And every time a new pop-up window is added, you need to check the logic of the previous pop-up window. Each step consumes development resources.

So our purpose is to solve, how to split the dependencies between each pop-up window, and display the pop-up windows in sequence at the right moment, liberate the glue code of when to show/when to hide.

demand analysis

The first is the needs of the pop-up window itself

  • popup display
  • Popup hidden
  • A pop-up window displays the conditions that need to be met

Then it's about pop-ups and pop-ups

  • Popup priority
  • Whether the popup will be affected by the displayed popup

The pop-up window display has a feature, that is, only one pop-up window is displayed at the same time, and it can be displayed one after another. If a queue is used for management, it is a matter of course that additional behaviors such as insertion, deletion, emptying, and traversal need to be handled.

This set of processes seems to be solved, but in fact, when the unified management of all pop-up windows is handed over to a scheduler, we must consider when it is more reasonable to show/hide these pop-up windows.

Of course, FGPopupScheduler can help with these trivial things, and more.

Implementation Analysis

Considering the diversity of the pop-up window itself, first of all, the abstract processing of the requirements required by the queue is put into it through the protocol <FGPopupView>.


@protocol FGPopupView <NSObject>

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

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

/*
 FGPopupSchedulerStrategyQueue会根据 -showPopupViewWithAnimation: 来做显示逻辑。如果block不传可能会出现意料外的问题
 */
- (void)showPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;

/*
 FGPopupSchedulerStrategyQueue会根据 -dismissPopupView: 做隐藏逻辑,如果含有动画请实现-dismissPopupViewWithAnimation:方法,如果block不传可能会出现意料外的问题
 */
- (void)dismissPopupViewWithAnimation:(FGPopupViewAnimationBlock)block;

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



/** 0.4.0 新增*/

/**
 FGPopupSchedulerStrategyQueue 会根据 - popupViewUntriggeredBehavior:来决定触发时弹窗的显示行为,默认为 FGPopupViewUntriggeredBehaviorAwait
 */
- (FGPopupViewUntriggeredBehavior)popupViewUntriggeredBehavior;


/**
 FGPopupViewSwitchBehavior 会根据 - popupViewSwitchBehavior:来决定已经显示的弹窗,是否会被后续更高优先级的弹窗锁影响,默认为 FGPopupViewSwitchBehaviorAwait  ⚠️⚠️ 只在FGPopupSchedulerStrategyPriority生效
 */
- (FGPopupViewSwitchBehavior)popupViewSwitchBehavior;

@end


复制代码

Regarding the order and priority of the pop-up window display, the actual operation will also involve the operation of inserting or removing in the middle. The data structure is more similar to the linked list, so the C++ STL standard library is used here: list.

The specific strategy is as follows

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

In fact, the user can also use it FGPopupSchedulerStrategyPriority | FGPopupSchedulerStrategyFIFOtogether to deal with how to decide the order of the same priority pop-up window when selecting the priority policy.

In addition, 0.4.0 added FGPopupViewUntriggeredBehavior and FGPopupViewSwitchBehavior

FGPopupViewUntriggeredBehavior is used to select whether it will be discarded directly if the display conditions are not met when the pop-up window is triggered in the response chain.

typedef NS_ENUM(NSUInteger, FGPopupViewUntriggeredBehavior) {
    FGPopupViewUntriggeredBehaviorDiscard,          //当弹窗触发显示逻辑,但未满足条件时会被直接丢弃
    FGPopupViewUntriggeredBehaviorAwait,          //当弹窗触发显示逻辑,但未满足条件时会继续等待
};
复制代码

FGPopupViewSwitchBehavior is used to handle whether the popup will be replaced by a higher priority popup lock when the popup is already displayed.

typedef NS_ENUM(NSUInteger, FGPopupViewSwitchBehavior) {
    FGPopupViewSwitchBehaviorDiscard,  //当该弹窗已经显示,如果后面来了弹窗优先级更高的弹窗时,显示更高优先级弹窗并且当前弹窗会被抛弃
    FGPopupViewSwitchBehaviorLatent,   //当该弹窗已经显示,如果后面来了弹窗优先级更高的弹窗时,显示更高优先级弹窗并且当前弹窗重新进入队列, PS:优先级相同时同 FGPopupViewSwitchBehaviorDiscard
    FGPopupViewSwitchBehaviorAwait,    //当该弹窗已经显示时,不会被后续高优线级的弹窗影响
};
复制代码

It hitTestsolves the requirements of the pop-up window display conditions. If the current pop-up window fails to pass hitTest, it will obtain the next pop-up window in the current list for testing according to the selected scheduler strategy.

- (PopupElement *)_hitTestFirstPopupResponder{
    PopupElement *element;
    for(auto itor=_list.begin(); itor!=_list.end();) {
        PopupElement *temp = *itor;
        id<FGPopupView> data = temp.data;
        __block BOOL canRegisterFirstPopupViewResponder = YES;
        if ([data respondsToSelector:@selector(canRegisterFirstPopupViewResponder)]) {
            canRegisterFirstPopupViewResponder = [data canRegisterFirstPopupViewResponder];
        }
        
        if (canRegisterFirstPopupViewResponder) {
            element = temp;
            break;
        }
        else if([data respondsToSelector:@selector(popupViewUntriggeredBehavior)] && [data popupViewUntriggeredBehavior] == FGPopupViewUntriggeredBehaviorDiscard){
            itor = _list.erase(itor++);
        }
        else{
            itor++;
        }
    }
    return element;
}
复制代码

Because FGPopupSchedulerof the unified management of all pop-up windows, when the pop-up window is triggered, the component itself needs to be processed. This author considers a total of 3 triggering situations

  • When adding a popup object
  • Monitor the idle time of the main thread through Runloop
  • User active trigger

Through the above three situations, almost all usage scenarios can be covered.

In addition, a state is added to the scheduler suspendedto actively suspend/restore the popup queue, which is used to control whether the current scheduler can trigger hitTestand display the logic.

Additionally the component supports thread safety. Considering that the timing of the operation may be in any thread,Components pthread_mutex_tensure thread safety by. ( pthread_mutex_tUnable to switch thread lock/unlock has been replaced with semaphore) It is worth noting that the display process of the popup window will be switched to the main thread, so no additional processing is required.

So far, the business of the entire component is relatively clear. FGPopupScheduler adopts the state mode. The component needs to allow these three processing methods to be freely changed, so it adopts the strategy mode to process. The following is the UML class diagram:

unnamed file(1).png

Guess you like

Origin juejin.im/post/6979459370807476261