preface

In objective-C projects, many developers may have written or seen code like this:


self.delegate = self

Copy the code

?? Set your own proxy as your own? Is this the right thing to do?

In this article, we’ll take a self-answering, easy-to-understand approach to discussing the wisdom of self.delegate = self, and the problems, if not the fatal problems, it can cause.

Why do you write that?

First of all, what’s the reason for the Delegate? And again, why do we write this way? And what are the scenes?

The author thinks that the Delegate mode is actually a derivative version of the NSProxy design mode. Their common characteristics can be understood as the message transmission of objects. The main differences are as follows:

  1. The two methods of message transmission are different. We use NSProxy to implement message forwarding, while Delegate does not generally implement message transmission.
  2. Delegate is one-to-one messaging (A->B), while NSProxy can be one-to-many messaging (A->B/A->C/A->D).

Self. Delegate = self sets the Delegate object as itself, eliminating the need to introduce A third party Delegate. This is mostly for convenience, and is common when using third-party closed source code and system classes (e.g. UITextField, etc.), because we cannot know how the internal message is delivered, we can only learn the message through the proxy object.

Self.delegate = self is not recommended by me because it can be a security risk (especially in projects that rely heavily on third-party libraries), as I’ll explain later.

This article takes the system class UITextField subclass as an example to discuss.

Inexplicable phenomenon

We often use the UITextField class or a subclass of it in our projects, sometimes setting the UITextField’s delegate as itself (self.delegate = self) for convenience. However, when using the UITextField control, It was found that the program did not respond. After a few seconds, the program flashed back.

Since there is a Bug, there is a Bug, so we start to investigate the cause (regardless of the call stack information) :

  1. First for the new part of the code for annotation, theself.delegate = selfComment out the code, rerunge the program, and find that the problem is fixed.
  2. Control variable method to start screening. Is itself.delegate = selfLead to?

Create a new project and write the same code: TXLimitedTextField

    
@implementation ViewController
   
- (void)viewDidLoad {
  [super viewDidLoad];
  
  TXLimitedTextField *textField = [[TXLimitedTextField alloc] initWithFrame:CGRectMake(100, 100, 100, 30)];
  textField.backgroundColor = [UIColor redColor];
  textField.delegate = textField;
  [self.view addSubview:textField];
}
    
@end
    
Copy the code

After running the newly built project, it was found that there was no such problem. Txlimitedtextfield. m = txLimitedTextField. m = TXLimitedTextField


@interface TXLimitedTextField ()

@end
    
@implementation TXLimitedTextField
    
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
   [textField endEditing:YES];
   return YES;
}
    
@end

Copy the code

Run the project, use TXLimitedTextField control, found that there is no problem.

What ‘s the fuck?? Original project code toxic??

After a global breakpoint, rerunge the project and find that the call stack recurses indefinitely until the stack overflows, causing the program to crash.

Here’s what causes it.

What is it?

Self. Delegate = self (self refers to TXLimitedTextField instance) call stack infinite recursion? So let’s look at TXLimitedTextField and see if there is any code in the project:

- (void)doSomething { if ([self.delegate respondsToSelector:@selector(doSomething)]) { [self.delegate performSelector:@selector(doSomething)]; }}Copy the code

First, it’s important to avoid this, especially if you’re using system classes or third-party closed source frameworks, because you don’t know how the implementation code is written.

If not, check to see if there is any UITextField runtime code or third-party frameworks such as BlocksKit, etc. Here is a specific example:

Recently, I was maintaining an old project, and the above problems were found in the project. After careful investigation, I found that BlocksKit was used in the project, including a Category (UITextField + BlocksKit). Which in view of the delegate to dynamically adjust UITextField at that time, the delegate replacing A2DynamicUITextFieldDelegate (parent classes for A2DynamicDelegate, root class NSProxy class) as the instance, The NSProxy class is mainly used for message forwarding (please refer to the official documentation if you are not familiar with it).

Breakpoint to the -respondStoSelector: method in the custom UITextField and the following method in the A2DynamicDelegate:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { A2BlockInvocation *invocation = nil; Else if (class_respondsToSelector(object_getClass(self), aSelector)) return [object_getClass(self) methodSignatureForSelector:aSelector]; return [[NSObject class] methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)outerInv { SEL selector = outerInv.selector; A2BlockInvocation *innerInv = nil; ... (slightly)} else if ([self. RealDelegate respondsToSelector: selector]) {[outerInv invokeWithTarget: self. RealDelegate]; } } - (BOOL)respondsToSelector:(SEL)selector { NSLog(@"%s--%@", __func__, NSStringFromSelector(aSelector)); return [self.invocationsBySelectors bk_objectForSelector:selector] || class_respondsToSelector(object_getClass(self), selector) || [self.realDelegate respondsToSelector:selector]; }Copy the code

We found that the program kept iterating through all four methods until the stack overflowed and the program crashed. I believe that you have encountered similar problems, the following author will take this example for specific analysis and investigate its reasons.

What’s the reason?

Find the program after the collapse of the point, through NSLog selector in the output of the above method selector, discovery is – keyboardInputChangedSelection: Method, then set the conditional breakpoints ([NSStringFromSelector (aSelector) isEqualToString: @ “keyboardInputChangedSelection:”]) as shown:

After entering breakpoint debugging, an interesting thing occurs, as shown in the figure below:

This shows that in UITextField, the pseudocode is as follows:

- (id)keyboardInputChangedSelection:(id)obj { // self == UITextField if ([self.delegate respondsToSelector:@selector(keyboardInputChangedSelection:)]) { [self.delegate keyboardInputChangedSelection:obj]; }}Copy the code

After seeing this method, the reader should find – keyboardInputChangedSelection: method with this section begins the proposed – doSomething: method of structure is the same? It’s just a different method name.

The TXLimitedTextField class (self.delegate = self) should also recurse indefinitely.

In fact, however, there is no loop.

The author through the breakpoint debugging, found TXLimitedTextField calls – keyboardInputChangedSelection: likewise, breakpoint above screenshot, but won’t appear dead cycle, eventually lead to the phenomenon of program crashes, the author forecast analysis, The UITextField class should do something special for self.delegate = self, which is up to apple’s dad. To be sure, there is no problem with an infinite loop in the absence of any method adjustment, i.e. “self.delegate == self”.

However, there is a method adjustment here, that is, BlocksKit dynamically replaces the delegate in the UITextField class (which also works in its subclass), so the delegate is actually an A2DynamicDelegate instance. To help readers understand, the author simply draws a diagram:

When clicking on the UITextField control the call stack looks like this (omitted) :

  1. [UITextField respondsToSelector:] // return YES
  2. [UITextField keyboardInputChangedSelection:]
  3. [TXLimitedTextField. Delegate respondsToSelector] / / due to the self. The realDelegate (realDelegate is A2DynamicDelegate instance in a weak reference object, BlocksKit () responds to this method, so return YES
  4. [TXLimitedTextField. Delegate keyboardInputChangedSelection:] / / A2DynamicDelegate actually has not come KeyboardInputChangedSelection: method, then entered the stage of forward
  5. [TXLimitedTextField.delegate methodSignatureForSelector:]
  6. [TXLimitedTextField. Delegate forwardInvocation:] / / the message forward again to the self. The realDelegate, then began to infinite loop.

How to solve it?

Based on the above discussion and analysis mainly taking UITextField as an example, how should this problem be solved?

  1. Avoid using it without first thinking it throughself.delegate = self.
  2. To break the dead-loop, just stop the message forwarding (although there are some minor issues), which can be handled in the -forwardInvocation: method.

As for how to deal with this problem in BlocksKit, the author has carried out a practice in an open source project. In the process of using InputKit, even if self.delegate = self, the above dead-loop problem will not occur. This article will not elaborate on it, see InputKit for details.

gossip

The author encountered the above problem, in fact, is indirectly caused by message forwarding, which can be said to be a Bug of the third-party framework BlocksKit. The author has not updated this framework for more than a year. (Perhaps because Swift no longer recommends the use of runtime-related methods, especially the API related to message forwarding. The author cannot update Objective-C lol).

advertising

Welcome to follow the wechat official account