类型判断上的小技巧
不知道大伙儿有没有这样的经历:
每次看到
1 | -[__NSCFConstantString objectForKey:]: unrecognized selector sent to instance 0x1050a6be8 |
1 | -[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance 0xb0000000000007b2 |
这样的crash是不是会有种想死的感觉。不管怎样,我每次碰到这种crash都会有中想死的冲动。虽然说这种crash修改起来超级容易,只是在进行操作前加上类型判断就可以,或者说是直接理解错误了变量的数据类型。
但是这种crash还是经常的出现,为什么呢?我思考了下,我觉得主要主要的原因有两个:
1、使用了“万能”的id;直接上例子代码吧
1 | id x = @"123"; |
如上的代码,开始笼统的将变量x定义为id类型的,后面又将x直接当做nsstring类型来做逻辑。也许一个人写的时候,他很清楚这块的逻辑。但是换一个人或者说是过段时间再来修改这块的代码。那么也许就会改成如下代码的可能性。因为这样子既没有报错,也没有warning。
1 | id x = @(123); |
这是一个原因,这个也是我在之前遇到过的。
2、使用自己服务器或者其他渠道的数据。
这点我相信只要做过非纯本地app的人,都深有感触。就是说虽然协议或者说约定好的x这个字段是string类型的。但是保不准到时候会出现错误传个int或者其他类型过来。记得我们cto大人说过,客户端不要轻易相信服务器给过来的数据;反之也是。其实这句话的有一层意思就是说,虽然我们大家都会按照定好的规则来执行。但是保不准有时候会出现问题。但是最终这些所有的问题都是需要由客户端来买单的。所以类型判断必不可少!
然而并不是所有的地方都会记得加上类型判断的。所以还是时不时会产生文字一开始说的那些crash。那么我就在想能不能一劳永逸,在最底层的地方加上类型判断之类的方法解决这个头痛的问题呢。
自然而然的想到了消息传递。其实通常的方法调用
1 | [someObject messageName:parameter]; |
这种方法调用就是消息传递,编译器看到这个会将其转化成如下
1 | objc_msgSend(someObject,@selector(messageName:),parameter); |
那么先说下向一个实例发送一个消息后,系统的处理是这样的:
1、发送消息([self sendMsg])
2、系统会check是否能够respnse这个消息
3、如果能response则调用想要方法,不能则抛出异常
而在第二步中,系统是如何check实例是否能response消息呢?如果实例没事就有想要的response,那么久会相应返回,如果没有系统就会发出methodSignatureForSelector消息,询问他这个消息是否有效?有效就返回相应的方法地址之类的信息,无效则返回nil。如果nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这是也就会crash,如果不是nil接着发送forwardInvocation消息。
看了上述的流程。那么是否只要在methodSignatureForSelector这一步,如果在确定本来返回的是nil的时候我自己构造一个将其返回是不是就可以不会闪退了呢。答案是:可以的。methodSignatureForSelector这一步其实也就是对方法的一个签名的返回。 大专栏 类型判断上的小技巧那么如果在这一步按照原有的签名生成的原则伪造一个假的签名,就可以蒙混过关。本会crash的方法将无响应。
以下的是我根据上面的思路写的一个nsobject的扩展类。重写了forwardInvocation和methodSignatureForSelector方法。而这里我只对了几个基础的类型做了检验。因为很多类型是ios非公开的,好像会有点问题。所以相当于先打个补丁在这里。
1 | -(void)forwardInvocation:(NSInvocation *)anInvocation |
下图是有关于消息传递的一张流程图
我觉得能够比较好的理解消息传递这一块
而这篇博文关于消息传递我觉得还是比较可以推荐的 https://www.zybuluo.com/MicroCai/note/64270
原文地址:https://www.cnblogs.com/sanxiandoupi/p/11692578.html
- Python:gethostbyname获取本地ip地址
- Oracle 12c系列(五)|PDB Refresh
- hdu 2473 Junk-Mail Filter (并查集之点的删除)
- Python:platform获取操作系统详细信息
- java版本的学生管理系统
- Python:获取目录下指定后缀的文件
- Go语言基于Socket编写服务器端与客户端通信的实例
- Linux强制用户首次登陆修改密码
- crontab执行python脚本提示ImportError解决方法
- Java学习之约瑟夫环的两中处理方法
- Zabbix实现QQ报警配置
- 限制用户使用su切换身份
- 微信小程序优秀开发资源汇总
- 编译安装openresty
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux下的多线程编程实例解析
- CentOS使用expect批量远程执行脚本和命令
- Centos8最小化部署安装OpenStack Ussuri的详细教程
- 详解Xshell 常见问题及相关配置
- linux安装部署ftp图片服务器的实现方法
- Centos7配置fastdfs和nginx分布式文件存储系统实现过程解析
- 解决Linux常用命令“ll”失效或命令未找到的问题
- 手把手教你在腾讯云上搭建hive3.1.2的方法
- centos6超20TB磁盘的分区格式化的示例代码
- Linux 系统双网卡绑定配置实现
- Linux系统设置开机自动运行脚本的方法实例
- Linux中fuser命令用法详解
- 在Ubuntu中实现人脸识别登录的完整步骤
- Linux下如何寻找相同文件的方法
- CentOS 7中Nginx日志定时拆分实现过程详解