专栏首页搜狗测试关于OC代理的问题:self.delegate = self

关于OC代理的问题:self.delegate = self

背景

最近作者在进行工程代码分析时,经常看到这样的代码:

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. 破除死循环,解决上述问题,只需停止消息转发即可。

本文分享自微信公众号 - 搜狗测试(SogouQA),作者:小K

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-01-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Swift| 基础语法(四)

    总结下 swift下的基础语法,里面涉及到:常量&变量、Swift中的数据类型、逻辑分支、循环、字符串相关、数组和字典、方法的书写调用等内容,考虑到阅读体验分多...

    進无尽
  • iOS 文本输入控制(献上框架)

    我们在业务开发中,往往会遇到需要限制文本输入的需求,比如只能输入数字、不能输入空格,稍微复杂一点的比如小数点后最多两位的价格输入。当然,若你的正则表达式玩儿得很...

    波儿菜
  • 深度定制化,啥都能做的委托模式

    在我们介绍具体的代码之前,我们先来设想一个场景。假设说你成了某知名电商某一个重要首页板块的负责人,你希望这个板块推荐的商品内容更加丰富,不仅包含推荐系统的结果,...

    TechFlow-承志
  • iOS框架·Masonry源码深度解析及学习启示:设计模式与链式编程思想

    可见,系统传统的代码布局有点繁琐。为了简化上述传统布局代码,被广泛应用的第三方框架 Masonry 对AutoLayout 进行了封装,Swift版则是 Sna...

    陈满iOS
  • AVFoundation 视频拍摄基础篇

    停止录制之后 可以在AVCaptureFileOutputRecordingDelegate回调方法中做对应的处理,比如视频转码,存入相册 等等。

    陈雨尘
  • 小萝莉说Crash(一):Unrecognized selector sent to instance xxxx

    大家好,我是来自Bugly Crash实验室的小萝莉(害羞ing),很高兴能和大家一起讨论关于移动终端App的Crash问题及解决方法。 在上次的“精神哥讲Cr...

    腾讯Bugly
  • JSPatch 实现原理详解

    JSPatch 是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用就可以使用 JavaScript 调用任何 Objective-C 原生接口...

    微信终端开发团队
  • ios学习——键盘的收起

      在开发过程中,我们经常会用到UITextField、UITextView等文本框,然后这些文本框在点击之后会自动成为第一响应者(FirstResponder...

    mukekeheart
  • iOS代理,通知,block的用法及不同

    在开发过程中,总是遇到不同页面之间传参问题,代理,通知,block 都可以实现这种简单功能,但是有时候都是根据自己的熟悉程度选择使用的方法,并没有深度的认识之间...

    honey缘木鱼

扫码关注云+社区

领取腾讯云代金券