从UIAlertController的封装看block的使用
看大佬的博客: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
这里在改进时,有几个问题出现:
- 是btn按钮,采用了链式编程的写法,也就是使用block作为参数,同时block内返回self对象;但是自己一直有一个困惑,目前还是没有具体的答案
疑惑:为什么OC中以block作为参数同时block内返回self对象的方法,可以使用点语法去调用?如果是其他的方法,只要 不满足block作为参数且block内返回self对象,则一定抛出警告
Property access result unused - getters should not be used for side effects
,这是为什么
-
用block保存响应事件的代码开block时,需要使用
copy
修饰.让其保存在堆中,否则在block中进行一些操作时,block可以已经被释放而直接抛出野指针错误; -
是网上看到的一个改正,调用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的嵌套,看着绕,但是看着牛逼啊...