背景
最近作者在进行工程代码分析时,经常看到这样的代码:
self.delegate = self //自己的代理设置为自己
于是心中产生了不少疑问,为什么会这样写?这样写是否是正确的?带着这些疑问,我去查找了一些资料并进行了整理,希望可以分享给大家。
原因
首先我们需要了解delegate到底是什么。
Delegate 模式其实就是 NSProxy 设计模式的一种衍生版,它们共同的特点可以理解为都是传递对象的消息,主要区别如下:
1. 两者消息传递方式不同,我们使用 NSProxy 会实现消息转发功能,而 Delegate 一般不会实现,仅作消息传递。
2. Delegate 是一对一的消息传递(A->B),而 NSProxy 可以一对多的进行消息传递(A->B/A->C/A->D)。
Delegate 无非就是把 A 的消息传递给代理对象 B,self.delegate = self 直接把代理对象设置为自己,这样省去了引入第三方代理,这种做法大部分情况是为了图个方便,一般出现在使用第三方闭源代码以及系统类(如:UITextField等)的情况下,因为我们无法获知内部消息是如何传递的,只能通过代理对象获知消息。
self.delegate = self 这种做法笔者并不推荐,因为它可能会带来一些安全隐患(特别是在依赖第三方库非常多的项目中)
问题
在项目中我们经常会用到 UITextField 类或者其子类,有时候为了图其方便会把 UITextField 的 delegate 设置为自己(self.delegate = self),然而在使用 UITextField 控件时,发现程序不响应了,过了几秒后程序出现闪退现象。
既然 Bug 来了,那当然就是找 Bug,于是我们开始排查原因(先撇开调用栈信息):
1. 首先针对新增的部分代码进行注释,把 self.delegate = self 代码注释掉,然后重新运行程序,发现问题得到解决。
2. 控制变量法开始排查。难道是 self.delegate = self 导致的?
于是新建工程,写了一份一模一样的代码(注:SGLimitedTextField 继承自 UITextField):
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; SGLimitedTextField *textField = [[SGLimitedTextField alloc] initWithFrame:CGRectMake(100, 100, 100, 30)]; textField.backgroundColor = [UIColor redColor]; textField.delegate = textField; [self.view addSubview:textField];} @end
运行新建的工程后,发现没有这问题。于是在 SGLimitedTextField.m 文件中再实现自己的代理方法:
@interface SGLimitedTextField ()
@end @implementation SGLimitedTextField - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField endEditing:YES]; return YES;} @end
运行工程,使用 SGLimitedTextField 控件,发现还是没有这问题。而进行全局断点后,重新再次运行项目,发现调用栈无限递归,直到栈溢出,最后导致程序崩溃。
原因
既然查到了无限递归,那我们就需要查找是否存在这种无限递归的代码
- (void)doSomething { if ([self.delegate respondsToSelector:@selector(doSomething)]) { [self.delegate performSelector:@selector(doSomething)]; }}
于是开始分析代码,找到了程序崩溃点,找到了程序的崩溃点后,通过 NSLog 输出上述方法中的选择器 selector,发现是 -keyboardInputChangedSelection: 方法,于是设置条件断点,如图所示:
进入断点调试后,发现一个有意思的事,如图所示:
这说明,在 UITextField 中,伪代码如下:
- (id)keyboardInputChangedSelection:(id)obj { // self == UITextField if ([self.delegate respondsToSelector:@selector(keyboardInputChangedSelection:)]) { [self.delegate keyboardInputChangedSelection:obj]; }}
此时,细心的读者可能会产生一个疑惑,如果如上所述,那么上文提到新建的工程(SGLimitedTextField 类,如果写了 self.delegate = self)也应该会出现无限递归(死循环)才对啊?
然而事实上却没发生死循环。
作者通过断点调试,发现同样会调用 -keyboardInputChangedSelection:,断点截图同上,但不会出现死循环,最终导致程序崩溃的现象,笔者猜测分析,UITextField 类应该针对 self.delegate = self 做了一些特殊的处理,具体什么处理,就得问苹果爸爸了。可以肯定的是,在没有任何方法调剂的情况下,即 “self.delegate == self”,是不会出现死循环的问题的。
问题解决
通过上文主要以 UITextField 为例进行讨论分析,那么这种问题应当如何解决?
1. 在没有考虑清楚前,避免使用 self.delegate = self。
2. 破除死循环,解决上述问题,只需停止消息转发即可。