从UIAlertController的封装看block的使用

2020-06-01  本文已影响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的嵌套,看着绕,但是看着牛逼啊...

上一篇下一篇

猜你喜欢

热点阅读