前言
现在APP在首页会有多个弹框按照顺序依次弹出的功能,比如强制更新、比如弹出消息推送的活动弹框、比如弹出搜索弹框之类的,一般情况下自定义的 Alert
弹框,你把它在为 keyWindow
的中作为subview
添加,这些弹框如果触发过程中同时触发,或者在其中一个弹框已经触发显示时,又触发了其他一个或者多个弹框,这时就会出现弹框叠加的一个效果。如果你用了毛玻璃背景,效果会更加明显,肯定不合适了。
所以,为了优化这个效果,我们需要当点击了第一个弹框的某个按钮之后,再弹出第二个弹框,以此类推。
这个就是我们实现的效果参考的demo
具体实现我们参考了这个demo
这个demo想到用信号量去解决,但是信号量会阻塞线程,不可以直接在主线程使用,所以需要在子线程控制信号量,在主线程创建和显示 Alert。
代码如下:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//创建一个队列,串行并行都可以,主要为了操作信号量
dispatch_queue_t queue = dispatch_queue_create("com.se7en.alert", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//创建一个初始为0的信号量
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//第一个弹框,UI的创建和显示,要在主线程
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"弹框1" message:@"第一个弹框" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
//点击Alert上的按钮,我们发送一次信号。
dispatch_semaphore_signal(sema);
}]];
[self presentViewController:alert animated:YES completion:nil];
});
//等待信号触发,注意,这里是在我们创建的队列中等待
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//上面的等待到信号触发之后,再创建第二个Alert
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"弹框2" message:@"第二个弹框" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
dispatch_semaphore_signal(sema);
}]];
[self presentViewController:alert animated:YES completion:nil];
});
//同理,创建第三个Alert
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"弹框3" message:@"第三个弹框" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
dispatch_semaphore_signal(sema);
}]];
[self presentViewController:alert animated:YES completion:nil];
});
});
}
但是我们实际的操作过程比这个要复杂一些
第一点,我们的弹框是通过事件触发的,而且不是一次性触发的
第二点,可能在触发的同时又的弹框是不触发的,而且在同一时间可能触发多次
所以这个demo 还不能完全拿到我们的项目中去使用,我们需要在实际的代码中继续优化。
项目实践
优化一:子线程唯一
为什么需要保证线程唯一呢?因为我们的这个弹框触发时机、是否同时触发多个弹框、以及在其中一个弹框弹出的同时是否还有其他弹框触发,我们都是不可知的,如果线程不能保证唯一,这样就无法用信号量加上的锁有效,所以,我们需要创建一个唯一的队列,并且是串行队列。
// 创建对象
@property (nonatomic,assign) dispatch_queue_t myQueue;
//重写get方法确保线程唯一
- (dispatch_queue_t)myQueue{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("alertViewDemo", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
优化二:将所有弹框封装在一个方法中
将所有的弹框方法按照顺序添加到一个总的方法中,并且给出一个总的完成回掉。
- (void)showSerialQueueAlertViewCompletion:(void(^)(BOOL finished))completion{
self.currentLoop++;
NSString *loopStr = [NSString stringWithFormat:@"当前第%ld轮弹框",self.currentLoop];
self.navigationItem.title = [NSString stringWithFormat:@"点击次数%ld",self.currentLoop];
NSLog(@"开始执行 弹框 :%@",[NSThread currentThread]);
//异步 这里会开启新的线程
//为保证 在同一个线程 必须确保queue 是同一个同步队列
dispatch_async(self.myQueue, ^{
//创建一个初始为0的信号量
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSLog(@"串行队列执行 第一个 弹框 :%@",[NSThread currentThread]);
//创建第1个Alert UI的创建和显示,要在主线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"串行队列 主线程 执行 第一个 弹框 :%@",[NSThread currentThread]);
if ([self showAlertView]) {
MyAdvertisementView *view2 = [[MyAdvertisementView alloc] init];
view2.title = [NSString stringWithFormat:@"%@,第一个",loopStr];
MyBaseAlertView *alertView2 = [[MyBaseAlertView alloc] init];
alertView2.animationType = MyBaseAlertViewAnimationTypeMoveFromBottomToBottom;
[alertView2 showContentView:view2];
[self.view addSubview:alertView2];
view2.actionBlock = ^(UIButton *sender) {
[alertView2 dissmissContentView];
//点击Alert上的按钮,我们发送一次信号。
dispatch_semaphore_signal(sema);
};
}else{
// 如果无需显示 直接发送信号量
dispatch_semaphore_signal(sema);
}
});
//等待信号触发,注意,这里是在我们创建的队列中等待
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(@"串行队列执行 第二个 弹框 :%@",[NSThread currentThread]);
//上面的等待到信号触发之后,再创建第二个Alert
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"串行队列 主线程 执行 第二个 弹框 :%@",[NSThread currentThread]);
if ([self showAlertView]) {
MyAdvertisementView *view2 = [[MyAdvertisementView alloc] init];
view2.title = loopStr;
MyBaseAlertView *alertView2 = [[MyBaseAlertView alloc] init];
alertView2.animationType = MyBaseAlertViewAnimationTypeMoveFromTopToTop;
[alertView2 showContentView:view2];
[self.view addSubview:alertView2];
view2.actionBlock = ^(UIButton *sender) {
[alertView2 dissmissContentView];
dispatch_semaphore_signal(sema);
};
}else{
dispatch_semaphore_signal(sema);
}
});
//同理,创建第N个Alert
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
if ([self showAlertView]) {
MyAdvertisementView *view2 = [[MyAdvertisementView alloc] init];
MyBaseAlertView *alertView2 = [[MyBaseAlertView alloc] init];
view2.title = loopStr;
alertView2.animationType = MyBaseAlertViewAnimationTypeMoveFromBottomToTop;
[alertView2 showContentView:view2];
[self.view addSubview:alertView2];
view2.actionBlock = ^(UIButton *sender) {
[alertView2 dissmissContentView];
dispatch_semaphore_signal(sema);
};
}else{
dispatch_semaphore_signal(sema);
}
});
//同理,创建第N个Alert
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
if ([self showAlertView]) {
MySuperSearchAlertView *view3 = [[MySuperSearchAlertView alloc] init];
view3.title = [NSString stringWithFormat:@"%@,最后一个",loopStr];
MyBaseAlertView *alertView3 = [[MyBaseAlertView alloc] init];
[alertView3 showContentView:view3];
[self.view addSubview:alertView3];
view3.actionBlock = ^(UIButton *sender) {
[alertView3 dissmissContentView];
dispatch_semaphore_signal(sema);
};
}else{
dispatch_semaphore_signal(sema);
}
});
// 最终执行完毕 执行完毕回掉 同时也是为了衔接最后一个alert 和 下一轮的第一个alert 不然会出现同时弹出两个alert 的情况
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(@"执行finished block 回掉");
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(sema);
if (completion) {
completion(YES);
}
});
});
NSLog(@"添加完所有提示框");
}
这个是我的demo