让UIKit更优雅易用:Block回调改造

背景

UIKit中的许多常用控件通过Delegate方式或者指定target+selector来实现事件回调,例如UITableViewUITextF让天秤倒追的星座ieldUIButt人体肠道结构示意图on等。这种方式的优点是代码规整,在代码量大的时候更容易维护。但是苹果x当回调逻辑不是特别复杂时,使用Block回调会比Delegatetarmacos系统get+selector更加有优势,具体体苹果xr现在:

  • 代码紧凑,无需苹果12声明协议,可以将相关代码逻辑集中在一起,降低开发调试成本;
  • 允许访问上下文变量,无需再专门抽出实例变量供不同代理方法共享。

苹果自身也曾经调整过部分API,专门支持Block回调方式,例如NSTimer,在iO苹果8plusS 10后新增了方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats Block:(void ()(NSTimer *timer))Block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

用来取代之前指定target+selector的方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

又例如iOS 9之前常用的UIAlertViewController,要通过UIAlertViewDelegate实现点击回调,苹果干脆废弃重写了一个类UIAlertController,抽出UIAlertAction类,完全通过Blockios系统式实现,代码写起来简洁明了很多,首先定义一个包含事件处理BlockUIAlertAction,再添加到UIAlert中:

///定义一个包含事件处理Block的UIAlertAction
+ (instancetype)actionWithTitle:(nullable NSString *)title style:(UIAlertActionStyle)style handler:(void ( __nullable)(UIAlertAction *action))handler;
///把action添加到UIAlertController
- (void)addAction:(UIAlertAction *)action;

UIControl也有同样的处理方式,封装了Block方式的事源码网站件处理回调,可惜只能在iO源码S 14后使用:

///定义一个包含事件处理Block的UIAction
+ (instancetype)actionWithHandler:(UIActionHandler)handler;
///使用UIAction代替之前的target+selector方式处理回调
- (void)addAction:(UIAction *)action forControlEvents:(UIControlEvents)controlEvents;

优化思路macoscatalina

鉴于上苹果8述分析,对UITableViewUITextField让天秤倒追的星座UIButton等常用的UIKit类进行Block改写,同时希望做到以下几点:

  • 苹果12Delegate的基础上增加macos和windows区别对应的Block方式,原有Delegate让天秤倒追的星座式不受影响,调用方可根据实际场景自行选择合适的回调方式;
  • Block的方法与原Delegaios越狱te方法名字尽量保持一致,降低迁移成本;
  • 赋值Block回调时,Xcode要能自动代码ios鲁多多app填充,因为手写Block入参回参容易出错;
  • 尽量不使用method swizzling等黑魔法,苹果13源码编辑器手机版下载安全性与稳定性的影响降到最小。

HWBlocksUI

基于上述目的,笔者封装了HWBlocksUI,对UITableView人头攒动的读音UITmacosxex让天秤难以放弃的星座tFieldUIControl所有子类如UIButtonUISwitch等常用UI组人头攒动件做了Block改造。

使用示例

UITableView实现一个简单列表:

    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
  [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseId];
  [self.view addSubview:tableView];
  NSArray *titles = @[@"北京", @"上海", @"深圳", @"广州", @"成都", @"雄安", @"苏州"];
  tableView.numberOfRowsHandler = ^NSInteger(UITableView *__weak _Nonnull tableView, NSInteger section) {
    return titles.count;
  };
 
  tableView.cellForRowHandler = UITableViewCell * _Nonnull(UITableView *__weak _Nonnull tableView, NSIndexPath * _Nonnull indexPath) {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath];
    cell.textLabel.text = titles[indexPath.row];
    return cell;
  };
  tableView.didSelectRowHandler = (UITableView *__weak _Nonnull tableView, NSIndexPath * _Nonnull indexPath) {
    NSString *title = titles[indexPath.row];
        NSLog(title);
  };

UITextField实现一个最多允许输入6个字符的苹果13输入框:

    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(20, 100, self.view.frame.size.width - 40, 30)];
  textField.borderStyle = UITextBorderStyleRoundedRect;
  textField.clearButtonMode = UITextFieldViewModeAlways;
  [self.view addSubview:textField];
  textField.shouldChangeCharactersHandler = BOOL(UITextField *__weak _Nonnull textField, NSRange range, NSString * _Nonnull replacementString) {
    NSString *str = [textField.text stringByReplacingCharactersInRange:range withString:replacementString];
    if (str.length > 6) {
      return NO;
    }
    return YES;
  };
  textField.shouldReturnHandler = BOOL(UITextField *__weak _Nonnull textField) {
    [textField resignFirstResponder];
    return YES;
  };

UIButton,考虑到对UIControlEventsTouchUpInside事件响应最多,所以专rtc是什么意思门封了一个clickHandler,对其他事件响应可以使用setHandler:forControlEvents:

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
  [btn setFrame:CGRectMake(24, 200, self.view.frame.size.width - 48, 20)];
  [btn setTitle:@"OK" forState:UIControlStateNormal];
    [self.view addSubview:btn];
  btn.clickHandler = {
    NSLog(@"OK");
  };
    [btn setHandler:{
            NSLog(@"touch down");
    } forControlEvents:UIControlEventTouchDown];

UISwitch,所有继承于UIios14.4.1更新了什么Control的类均可支持:

    UISwitch *aSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(0, 100, 100, 20)];
    aSwitch.on = YES;
    [self.view addSubview:aSwitch];
    [aSwitch setHandler:{
        NSLog(@"switch value changed");
    } forControlEvents:UIControlEventValueChanged];

实现原理

HWBlocksUI对UIKit进行Block改造的核心点在于:

  • 为要改人头攒动造的UIKit类,添加每个Delegate方法对应的Block属性;
  • 由于无法改造UIKit源码,所以仍然需要有一个Delegatemacoscatalina象,实现对应的代理方法;
  • Delegate对象在执行代理方法时,找到对应的Block执行实际回调方法macos是什么意思
  • 对调用方隐ios系统藏这个Delegate对象;

下面以UITextField为例看下改造的主要过程:

添加Blrtc是什么意思ock属性

定义相应Cat源码之家egoryUITextField+HWBlocksUI用来绑定Blockmacos系统;梳理UITextFieldDelegate的方法,定义对应的BlockBlock属性名采用Delegate的方法主体名+Handler的形式,入参和回参与Delegate方法保持一致,通过runtime将该Block属性添加到该分类。示例代码如下:

头文件中定义Block属性:

typedef BOOL(HWShouldBeginEditingBlock)(UITextField *__weak textField);
@property (nonatomic, copy) HWShouldBeginEditingBlock shouldBeginEditingHandler;

实现文件中,实现其对应的setterget苹果xrter

- (void)setShouldBeginEditingHandler:(HWShouldBeginEditingBlock)shouldBeginEditingHandler {
  NSAssert(shouldBeginEditingHandler, @"shouldBeginEditingHandler cannot be nil");
  [self configDelegate];
  objc_setAssociatedObject(self, HWBlocksUIShouldBeginEditingKey, shouldBeginEditingHandler, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (HWShouldBeginEditingBlock)shouldBeginEditingHandler {
  return objc_getAssociatedObject(self, &HWBlocksUIShouldBeginEditingKey);
}

这里setterRTC中会同时执行[self configDelegate],接下来会讲到其目源码时代的。

配置Delegte

新增一个类HWBlocksUIProxy,遵ios下载UI源码交易平台TextFieldDelegate,在其代理方苹果x法中,实际执行的是该对象绑定的Block,如果没RTC有找到对应的Block,则返回默认值:

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
  if (textField.shouldBeginEditingHandler) {
    return textField.shouldBeginEditingHandler(textField);
  }
  return YES;
}

在上步设置Block苹果7性时,会把Delegate设置为该HWBlocksUIProxy

- (void)configDelegate {
  HWBlocksUIProxy *Delegate = [HWBlocksUIProxy sharedInstance];
  if (self.Delegate != Delegate) {
    self.Delegate = Delegate;
  }
}

对调用方隐藏Delegate

由于在每一次设置Block时,都会去检查设苹果官网Delegate,所以达到了对调用方隐藏Delegate的目的。考虑到HWBlockios12sUIProxy让天秤座低头的星座使用特征和频率,同时由于其不包含实例变量,只用来转发方法,资源占用很小,方便起见设为单例形式。

内存处理

typedef BOOL(HWShouldChangeCharactersBlock)(UITextField *__weak textField, NSRange range, NSString *replacementString);

定义Block时,UIKit对象自身需要设置为__w苹果12eak属性,以防出现UIKit对象与人体肠道结构示意图其持有Block之间的循环应用。

总结

HWBlocksUI的实现大部分是胶水代码,不过如果能让调用方更方便使用,维护代价ios12更小,那这一切都是值得做的。欢迎各位大佬一起讨论、使用、改进。

发表回复

提供最优质的资源集合

立即查看 了解详情