美文网首页
从UIAlertController的封装看block的使用

从UIAlertController的封装看block的使用

作者: 越来越胖了 | 来源:发表于2020-06-01 20:12 被阅读0次

    看大佬的博客:https://www.jianshu.com/p/ae336594daf0后自己的理解然后写的一些东西
    这个是我写的测试代码 DEMO 密码: v8sk

    一开始是单纯的想封装一个简易的UIAlertController,不想像下面这样写一堆的代码:

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"提示", @"提示") message:NSLocalizedString(@"没有数据", @"没有数据") preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"取消", @"取消") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                [alertController dismissViewControllerAnimated:YES completion:nil];
            }];
            [alertController addAction:cancelAction];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self presentViewController:alertController animated:YES completion:nil];
            });
    
    

    所以一个最简陋的封装出现了:

    /// 提示框-->OC的调用
    /// @param controller 要显示的VC
    /// @param title 标题
    /// @param message 信息
    /// @param btnTitArray 按钮集合
    /// @param handler 响应Block
    + (void)showAlertInController:(UIViewController * __nullable)controller
                            Title:(NSString *__nullable)title
                          Message:(NSString * __nullable)message
                ButtonsTitleArray:(NSArray *)btnTitArray
                          handler:(void(^)(int selectedIndex))handler;
    
    #import "CRAlertShow.h"
    
    @implementation CRAlertShow
    
    + (void)showAlertInController:(UIViewController *)controller Title:(NSString *)title Message:(NSString *)message ButtonsTitleArray:(NSArray *)btnTitArray handler:(void (^)(int))handler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
                                                                                 message:message
                                                                          preferredStyle:UIAlertControllerStyleAlert ];
        //空按钮集合
        if (btnTitArray.count<1) {
            return;
        }
        
        //分配事件
        for (int i=0; i<btnTitArray.count; i++) {
            NSString *btnTitle=btnTitArray[i];
            UIAlertAction *actions = [UIAlertAction actionWithTitle:btnTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *  action){
                if (handler) {
                   handler(i);
                }
            }];
            [alertController addAction:actions];
        }
        
        //获取根控制器进行跳转弹出事件
        UIViewController *rootViewController = nil;
        if (@available(iOS 13.0, *)) {
            rootViewController = [UIApplication sharedApplication].windows[0].rootViewController;
        }else{
            rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
        }
        rootViewController = controller==nil? rootViewController : controller;
        
        //查看根控制器是否已推出控制器,如果有,在已推出控制器消失前,无法推出新的控制器。
        UIViewController *presentedVC = rootViewController.presentedViewController;
        
        if (presentedVC) {
            NSLog(@"error:已经有一个推出的控制器:%@",[presentedVC class]);
            return;
        }
        [rootViewController presentViewController:alertController animated:YES completion:nil];
        
    }
    
    @end
    
    

    这个时候的使用是这样的:

       [CRAlertShow showAlertInController:self Title:@"提示" Message:@"你要Kill库克吗?" ButtonsTitleArray:@[@"确定",@"取消"] handler:^(int selectedIndex) {
           switch (selectedIndex) {
               case 0:
                   NSLog(@"点击了确定");
                   break;
               case 1:
                   NSLog(@"点击了取消");
                   break;
                   
               default:
                   break;
           }
           
       }];
    
    

    本来到这里就玩了的,但是

    看过大佬的代码,可以用C再包装一下,调用时看起来就比较牛逼了,于是改进一下

    改进版本一:

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    //typedef void(^sureBtnClickBlock)(void);
    
    NS_ASSUME_NONNULL_BEGIN
    
    #define jxt_dispatch_main_async_safe(block)\
        if ([NSThread isMainThread]) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    /**
     回调主线程(显示alert必须在主线程执行)
    
     @param block 执行块
     */
    static inline void jxt_getSafeMainQueue(_Nonnull dispatch_block_t block)
    {
        jxt_dispatch_main_async_safe(block);
    }
    
    typedef void(^ZSYAlertClickBlock)(NSInteger buttonIndex);
    
    /// C的提示框
    /// @param title 标题
    /// @param message 主题信息
    /// @param sureBtn 确定
    /// @param sureBlock 确定的事件block
    /// @param cancelBtn 取消
    /// @param cancelBlcok 取消的事件block
    /// @param controller 加载视图的VC
    void zsy_showAlertWithTwoBtn(NSString * title,
                                 NSString *message,
                                 NSString *sureBtn,
                                 ZSYAlertClickBlock sureBlock,
                                 NSString *cancelBtn,
                                 ZSYAlertClickBlock cancelBlcok,
                                 UIViewController *controller);
    
    
    
    @interface CRAlertShow : NSObject
    
    
    /// 提示框-->OC的调用
    /// @param controller 要显示的VC
    /// @param title 标题
    /// @param message 信息
    /// @param btnTitArray 按钮集合
    /// @param handler 响应Block
    + (void)showAlertInController:(UIViewController * __nullable)controller
                            Title:(NSString *__nullable)title
                          Message:(NSString * __nullable)message
                ButtonsTitleArray:(NSArray *)btnTitArray
                          handler:(void(^)(int selectedIndex))handler;
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "CRAlertShow.h"
    
    void zsy_showAlertWithTwoBtn(NSString * title,NSString *message,NSString *sureBtn,ZSYAlertClickBlock sureBlock,NSString *cancelBtn,ZSYAlertClickBlock cancelBlcok,UIViewController *controller){
        
        jxt_getSafeMainQueue(^{
            [CRAlertShow showAlertInController:controller Title:title Message:message ButtonsTitleArray:@[sureBtn,cancelBtn] handler:^(int selectedIndex) {
                if (selectedIndex ==0) {
                    sureBlock(selectedIndex);
                }else if(selectedIndex ==1){
                    cancelBlcok(selectedIndex);
                }
                
            }];
        });
        
    }
    
    
    @implementation CRAlertShow
    
    + (void)showAlertInController:(UIViewController *)controller Title:(NSString *)title Message:(NSString *)message ButtonsTitleArray:(NSArray *)btnTitArray handler:(void (^)(int))handler{
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
                                                                                 message:message
                                                                          preferredStyle:UIAlertControllerStyleAlert ];
        //空按钮集合
        if (btnTitArray.count<1) {
            return;
        }
        
        //分配事件
        for (int i=0; i<btnTitArray.count; i++) {
            NSString *btnTitle=btnTitArray[i];
            UIAlertAction *actions = [UIAlertAction actionWithTitle:btnTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *  action){
                if (handler) {
                   handler(i);
                }
            }];
            [alertController addAction:actions];
        }
        
        //获取根控制器进行跳转弹出事件
        UIViewController *rootViewController = nil;
        if (@available(iOS 13.0, *)) {
            rootViewController = [UIApplication sharedApplication].windows[0].rootViewController;
        }else{
            rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
        }
        rootViewController = controller==nil? rootViewController : controller;
        
        //查看根控制器是否已推出控制器,如果有,在已推出控制器消失前,无法推出新的控制器。
        UIViewController *presentedVC = rootViewController.presentedViewController;
        
        if (presentedVC) {
            NSLog(@"error:已经有一个推出的控制器:%@",[presentedVC class]);
            return;
        }
        [rootViewController presentViewController:alertController animated:YES completion:nil];
        
    }
    
    @end
    
    

    然后调用就变成这样了

    zsy_showAlertWithTwoBtn(@"提示", @"你要Kill库克吗?", @"是的", ^(NSInteger buttonIndex) {
            NSLog(@"确定----->");
        }, @"取消", ^(NSInteger buttonIndex) {
            NSLog(@"取消----->");
        }, self);
    
    

    但是我发现一个问题,就是大佬写的封装,并不需要传入VC,就想是iOS8之前可以直接show一样,UIAlertController不是需要一个VC进行[VC presentViewController:alertMaker animated:YES completion:NULL];嘛,看过大佬的代码,发现是写了一个VC的分类,在分类中处理了,所以再次进行改进

    改进二:

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZSYAlertController_old : UIAlertController
    
    //这两种写法是一样的----->本质就是一个block作为返回值
    -(ZSYAlertController_old *(^)(NSString *title))addActionDefaultTitle;
    
    -(ZSYAlertController_old *(^)(NSString *title))addActionCancelTitle;
    
    @end
    
    
    #pragma mark - UIViewController扩展使用ZSYAlertController
    
    //在block中 链式构造 按钮title
    typedef void(^ZSYAlertAppearanceProcess)(ZSYAlertController_old *alertMaker);
    
    //这个是最后事件的响应block
    typedef void (^JXTAlertActionBlock)(NSInteger buttonIndex);
    
    @interface UIViewController (ZSYAlertController_old)
    
    - (void)old_showAlertWithTitle:(nullable NSString *)title
                           message:(nullable NSString *)message
                 appearanceProcess:(ZSYAlertAppearanceProcess)appearanceProcess
                      actionsBlock:(nullable JXTAlertActionBlock)actionBlock;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "ZSYAlertController_old.h"
    
    
    #pragma mark - ZSYAlertController
    
    @interface ZSYAlertController_old ()
    
    @property (nonatomic, strong) NSMutableArray <NSString *>* jxt_alertActionArray;
    
    @property (nonatomic, copy) void(^ActionClickBlock)(NSInteger index);
    
    @end
    
    @implementation ZSYAlertController_old
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    - (NSMutableArray<NSString *> *)jxt_alertActionArray{
        if (_jxt_alertActionArray == nil) {
            _jxt_alertActionArray = [NSMutableArray array];
        }
        return _jxt_alertActionArray;
    }
    
    -(ZSYAlertController_old *(^)(NSString *title))addActionDefaultTitle{
        return ^(NSString *title){
            [self.jxt_alertActionArray addObject:title];
            return self;
        };
        
    }
    -(ZSYAlertController_old *(^)(NSString *title))addActionCancelTitle{
        return ^(NSString *title){
            [self.jxt_alertActionArray addObject:title];
            return self;
        };
    }
    
    
    /// 创建UIAlertController对象
    /// @param title 标题
    /// @param message 信息
    /// @param preferredStyle 类型
    - (instancetype)initAlertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle
    {
        if (!(title.length > 0) && (message.length > 0) && (preferredStyle == UIAlertControllerStyleAlert)) {
            title = @"";
        }
        self = [[self class] alertControllerWithTitle:title message:message preferredStyle:preferredStyle];
        
    //    self = [ZSYAlertController alertControllerWithTitle:title message:message preferredStyle:preferredStyle];
        
        if (!self) return nil;
        
        return self;
    }
    
    //这样的写法,大家应该都能想到;然后就是问题的出现,如何把这个响应的事件传递出去
    //解决方法一:是直接使用block属性,把index传进去
    -(void)alertActionsConfig_old{
        if (self.jxt_alertActionArray.count > 0){
            //创建action
            __weak typeof(self) weakSelf = self;
            [self.jxt_alertActionArray enumerateObjectsUsingBlock:^(NSString * _Nonnull title, NSUInteger idx, BOOL * _Nonnull stop) {
                UIAlertAction *alertAction = [UIAlertAction actionWithTitle:title style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
                    __strong typeof(self) strongSelf = weakSelf;
                    if (strongSelf.ActionClickBlock) {
                        strongSelf.ActionClickBlock(idx);
                    }
                }];
                //action作为self元素,其block实现如果引用本类指针,会造成循环引用
                [self addAction:alertAction];
            }];
        }
    }
    
    @end
    
    
    #pragma mark - UIViewController扩展
    
    @implementation UIViewController (ZSYAlertController_old)
    
    -(void)old_showAlertWithTitle:(NSString *)title message:(NSString *)message appearanceProcess:(ZSYAlertAppearanceProcess)appearanceProcess actionsBlock:(nullable JXTAlertActionBlock)actionBlock{
        
        if (appearanceProcess)
        {
            ZSYAlertController_old *alertMaker = [[ZSYAlertController_old alloc] initAlertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
            //防止nil
            if (!alertMaker) {
                return ;
            }
            //加工链
            appearanceProcess(alertMaker);
            
            //配置响应
            alertMaker.ActionClickBlock = actionBlock;
            [alertMaker alertActionsConfig_old];
            
            [self presentViewController:alertMaker animated:YES completion:NULL];
            
        }
    }
    
    @end
    
    

    这里在改进时,有几个问题出现:

    1. 是btn按钮,采用了链式编程的写法,也就是使用block作为参数,同时block内返回self对象;但是自己一直有一个困惑,目前还是没有具体的答案

    疑惑:为什么OC中以block作为参数同时block内返回self对象的方法,可以使用点语法去调用?如果是其他的方法,只要 不满足block作为参数且block内返回self对象,则一定抛出警告Property access result unused - getters should not be used for side effects,这是为什么

    1. 用block保存响应事件的代码开block时,需要使用copy修饰.让其保存在堆中,否则在block中进行一些操作时,block可以已经被释放而直接抛出野指针错误;

    2. 是网上看到的一个改正,调用block前,创建一个临时的变量来保存我们的block,就是改成如下:

    [self.jxt_alertActionArray enumerateObjectsUsingBlock:^(NSString * _Nonnull title, NSUInteger idx, BOOL * _Nonnull stop) {
                UIAlertAction *alertAction = [UIAlertAction actionWithTitle:title style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
    //                __strong typeof(self) strongSelf = weakSelf;
    //                if (strongSelf.ActionClickBlock) {
    //                    strongSelf.ActionClickBlock(idx);
    //                }
                    
                    
                    void(^ActionClickBlock_new)(NSInteger index) = self->_ActionClickBlock;
                    if (ActionClickBlock_new) {
                        ActionClickBlock_new(idx);
                    }
                }];
                //action作为self元素,其block实现如果引用本类指针,会造成循环引用
                [self addAction:alertAction];
            }];
    

    给出的解释是因为if(ActionClickBlock)只能保证在你执行if判断时候你的actionBlock不为空,可是有可能在你执行完if判断后被其他线程中的操作修改掉ActionClickBlock,这个应该是一个安全性的问题了,这样做比较严谨吧;

    改进三:

    我们可以把外界处理action的block代码块作为一个参数,在UIAlertController的action方法中直接调用,其实就是一个链式编程的简单的使用,block的参数还是一个block而已,如果对链式编程理解的话就很好理解,改进如下:

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @class ZSYAlertController;
    
    //一个block,用于设置 按钮名称,返回 ZSYAlertController 对象
    typedef ZSYAlertController * _Nonnull (^AlertTitle)(NSString *title);
    
    
    
    
    @interface ZSYAlertController : UIAlertController
    
    
    /// 设置第一个按钮的标题,返回 返回 ZSYAlertController 对象 -------> 链式编程的写法
    -(AlertTitle)addActionDefaultTitle;
    
    -(AlertTitle)addActionCancelTitle;
    
    //这两种写法是一样的----->本质就是一个block
    
    //-(ZSYAlertController *(^)(NSString *title))defaultTitle;
    //
    //-(ZSYAlertController *(^)(NSString *title))CancelTitle;
    
    #pragma mark - 下面三个为测试方法
    -(ZSYAlertController *)test;
    
    -(ZSYAlertController *)test1;
    
    -(void (^)(NSString *title))test3;
    
    @end
    
    
    #pragma mark - UIViewController扩展使用ZSYAlertController
    
    //在block中 链式构造 按钮title
    typedef void(^AlertAppearanceProcess)(ZSYAlertController *alertMaker);
    //这个是最后事件的响应block
    typedef void (^JXTAlertActionBlock)(NSInteger buttonIndex);
    
    @interface UIViewController (ZSYAlertController)
    
    - (void)jxt_showAlertWithTitle:(nullable NSString *)title
                           message:(nullable NSString *)message
                 appearanceProcess:(AlertAppearanceProcess)appearanceProcess
                      actionsBlock:(nullable JXTAlertActionBlock)actionBlock;
    
    @end
    
    
    
    NS_ASSUME_NONNULL_END
    
    #import "ZSYAlertController.h"
    
    #pragma mark - ZSYAlertController
    
    //这个是提示框的事件的配置,传入的参数是一个block,--->这个block是外部给出具体的代码块
    typedef void (^JXTAlertActionsConfig)(JXTAlertActionBlock actionBlock);
    
    @interface ZSYAlertController ()
    
    @property (nonatomic, strong) NSMutableArray <NSString *>* jxt_alertActionArray;
    
    //action配置
    - (JXTAlertActionsConfig)alertActionsConfig;
    
    @end
    
    @implementation ZSYAlertController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    - (NSMutableArray<NSString *> *)jxt_alertActionArray{
        if (_jxt_alertActionArray == nil) {
            _jxt_alertActionArray = [NSMutableArray array];
        }
        return _jxt_alertActionArray;
    }
    
    -(AlertTitle)addActionDefaultTitle{
        return ^(NSString *title){
            [self.jxt_alertActionArray addObject:title];
            
            return self;
        };
        
    }
    -(AlertTitle)addActionCancelTitle{
        return ^(NSString *title){
            [self.jxt_alertActionArray addObject:title];
            return self;
        };
    }
    
    
    /// 创建UIAlertController对象
    /// @param title 标题
    /// @param message 信息
    /// @param preferredStyle 类型
    - (instancetype)initAlertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(UIAlertControllerStyle)preferredStyle
    {
        if (!(title.length > 0) && (message.length > 0) && (preferredStyle == UIAlertControllerStyleAlert)) {
            title = @"";
        }
        self = [[self class] alertControllerWithTitle:title message:message preferredStyle:preferredStyle];
        
    //    self = [ZSYAlertController alertControllerWithTitle:title message:message preferredStyle:preferredStyle];
        
        if (!self) return nil;
        
        return self;
    }
    
    
    //action配置
    - (JXTAlertActionsConfig)alertActionsConfig{
        return ^(JXTAlertActionBlock actionBlock) {
            if (self.jxt_alertActionArray.count > 0){
                //创建action
                [self.jxt_alertActionArray enumerateObjectsUsingBlock:^(NSString * _Nonnull title, NSUInteger idx, BOOL * _Nonnull stop) {
                    UIAlertAction *alertAction = [UIAlertAction actionWithTitle:title style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
                        if (actionBlock) {
                            actionBlock(idx);
                        }
                    }];
                    //action作为self元素,其block实现如果引用本类指针,会造成循环引用
                    [self addAction:alertAction];
                }];
            }
        };
    }
    
    @end
    
    #pragma mark - UIViewController扩展
    
    @implementation UIViewController (ZSYAlertController)
    
    -(void)jxt_showAlertWithTitle:(NSString *)title message:(NSString *)message appearanceProcess:(AlertAppearanceProcess)appearanceProcess actionsBlock:(nullable JXTAlertActionBlock)actionBlock{
        
        if (appearanceProcess)
        {
            ZSYAlertController *alertMaker = [[ZSYAlertController alloc] initAlertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
            //防止nil
            if (!alertMaker) {
                return ;
            }
            //加工链
            appearanceProcess(alertMaker);
            
            //配置响应,就是链式编程的一个方法调用,只是这里就一个函数调用,不需要成链,所以也就没有返回self实例对象
            alertMaker.alertActionsConfig(actionBlock);
            
            [self presentViewController:alertMaker animated:YES completion:NULL];
            
        }
        
        
    }
    @end
    
    

    比较难想到的就是 处理UIAlertAction的 block中的参数是一个外界具体处理action的block代码块,有点绕,所以多琢磨下.
    最后的使用是如下:

     [self jxt_showAlertWithTitle:@"提示" message:@"N多block处理的Alert提示框" appearanceProcess:^(ZSYAlertController * _Nonnull alertMaker) {
            alertMaker.addActionDefaultTitle(@"确定").addActionCancelTitle(@"取消");
            //alertMaker.test.test1;
            //(void)alertMaker.test3(@"name老王");
        } actionsBlock:^(NSInteger buttonIndex) {
            if (buttonIndex == 0) {
                NSLog(@"确定");
            }
            else if (buttonIndex == 1) {
                NSLog(@"取消");
            }
        }];
    

    个人感觉,改进二应该是大部分人的做法,而改进三进行block的嵌套,看着绕,但是看着牛逼啊...

    相关文章

      网友评论

          本文标题:从UIAlertController的封装看block的使用

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