background

Many common controls in UIKit implement event callbacks through Delegate mode or specify target+selector, such as UITableView, UITextField, UIButton, etc. The advantage of this approach is that the code is neat and easier to maintain when there is a lot of code. However, when the callback logic is not particularly complex, using a Block callback has some advantages over Delegate or target+selector.

  • Compact code, no need to declare the protocol, can be related to the code logic together, reduce the cost of development and debugging;
  • Allows access to context variables without having to extract instance variables to be shared by different proxy methods.

Apple itself has tweaked some of its apis to support Block callbacks, such as NSTimer, which added methods after iOS 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));
Copy the code

To replace the selector method previously specified:

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

Another example is UIAlertViewController, which was used before iOS 9, and to implement callbacks via UIAlertViewDelegate, Apple scrapped a UIAlertController class, pulled out UIAlertAction, Complete Block implementation, code to write a lot of concise:

+ (instancetype)actionWithTitle:(nullable NSString *)title style:(UIAlertActionStyle)style handler:(void (^ __nullable)(UIAlertAction *action))handler;
Copy the code

Optimization idea

In view of the above analysis, the Block rewriting of UITableView, UITextField, UIButton and other commonly used UIKit classes is expected to do the following:

  • inDelegateOn the basis of the corresponding increaseBlockManner, originalDelegateThe callback mode is not affected. The caller can select an appropriate callback mode based on the actual scenario.
  • BlockThe method and the originalDelegateMethod names should be kept consistent to reduce migration costs.
  • The assignmentBlockWhen the callback,XcodeTo be able to automatically code fill, because handwritingBlockInput and return parameters are prone to error;
  • Use as little as possiblemethod swizzlingAnd other dark magic to minimize the impact on security and stability.

HWEasyUI

Based on the above purpose, the author encapsulated HWEasyUI library, to UITableView, UITextField, UIButton commonly used UI components to do Block transformation, use the following example:

UITableView implements a simple list:

    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseId];
    [self.view addSubview:tableView];

    NSArray *titles= @ [@ "Beijing," @ "Shanghai," @ "shenzhen" @ "in guangzhou," @ "in chengdu," @ "male" Ann. @ "suzhou");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);
    };
Copy the code

UITextField implements an input field that allows up to 6 characters:

    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;
    };
Copy the code

UIButton, considering most of UIControlEventsTouchUpInside incident response, so the special seal for a clickHandler, response can be used for other events setEventsHandler: forControlEvents: :

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setFrame:CGRectMake(24.200.self.view.frame.size.width - 48.20)];
    [btn setTitle:@"OK" forState:UIControlStateNormal];
    btn.clickHandler = ^{
        NSLog(@"OK");
    };
    [self.view addSubview:btn];
Copy the code

Realize the principle of

The core of UIKit Block modification is:

  • To be reformedUIKitClass, add the correspondingBlockProperties;
  • Because it can’t be modifiedUIKitSource code, so you still need to have oneDelegateObject to implement the corresponding proxy method;
  • DelegateObject finds the corresponding when executing the proxy methodBlockExecute the actual callback method;
  • Hide this from the callerDelegateObject;

The following takes UITextField as an example to look at the main process of transformation:

Add a Block attribute

Add the corresponding category and bind the Block to runtime. Note that the name and parameters of the Block must be consistent with the Delegate method. Define Block attributes in header files:

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

In the implementation file, implement the corresponding setter and getter:

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

- (HWShouldBeginEditingBlock)shouldBeginEditingHandler {
    return objc_getAssociatedObject(self.&HWEasyUIShouldBeginEditingKey);
}

Copy the code

Here, the setter will execute [self configDelegate] at the same time, and we’ll talk about the purpose.

Configuration Delegte

A new class, HWEasyUIProxy, follows UITextFieldDelegate, whose delegate method actually executes the Block bound to the object, and returns the default if no corresponding Block is found:

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

When you set the Block property in the previous step, you set the Delegate to the HWEasyUIProxy:

- (void)configDelegate {
    HWEasyUIProxy *Delegate = [HWEasyUIProxy sharedInstance];
    if (self.Delegate ! = Delegate) {
        self.Delegate = Delegate; }}Copy the code

Hide the Delegate from the caller

Because every time a Block is set, a check is made to check the set Delegate, it is possible to hide the Delegate from the caller. Considering the usage characteristics and frequency of HWEasyUIProxy, and since it does not contain instance variables and is only used for forwarding methods, it is convenient to use the singleton form.

Memory processing

typedef BOOL(^HWShouldChangeCharactersBlock) (UITextField *__weak textField, NSRange range, NSString *replacementString);
Copy the code

When defining blocks, UIKit objects themselves need to be set to an __weak property to prevent loops between UIKit objects and their holding blocks.

conclusion

The implementation of HWEasyUI is mostly glue code, but it’s all worth it if it makes it easier for callers to use and less expensive to maintain. Welcome to discuss, use and improve.