ios runtime基础应用

引言

相信很多同学都听过运行时,但是我相信还是有很多同学不了解什么是运行时,到底在项目开发中怎么用?什么时候适合使用?想想我们的项目中,到底在哪里使用过运行时呢?还能想起来吗?另外,在面试的时候,是否经常有笔试中要求运用运行时或者在面试时面试官会问是否使用过运行时,又是如何使用的?

回想自己,曾经在面试中被面试官拿运行时刁难过,也在笔试中遇到过。因此,后来就深入地学习了Runtime机制,学习里面的API。所以才有了后来的组件封装中使用运行时。

相信我们都遇到过这样一个问题:我想在扩展(category)中添加一个属性,如果iOS是不允许给扩展类扩展属性的,那怎么办呢?答案就是使用运行时机制

运行时机制

Runtime是一套比较底层的纯C语言的API, 属于C语言库, 包含了很多底层的C语言API。 在我们平时编写的iOS代码中, 最终都是转成了runtime的C语言代码。

所谓运行时,也就是在编译时是不存在的,只是在运行过程中才去确定对象的类型、方法等。利用Runtime机制可以在程序运行时动态修改类、对象中的所有属性、方法等。

还记得我们在网络请求数据处理时,调用了-setValuesForKeysWithDictionary:方法来设置模型的值。这里什么原理呢?为什么能这么做?其实就是通过Runtime机制来完成的,内部会遍历模型类的所有属性名,然后设置与key对应的属性名的值。

我们在使用运行时的地方,都需要包含头文件:#import <objc/runtime.h>。如果是Swift就不需要包含头文件,就可以直接使用了。

获取对象所有属性名


利用运行时获取对象的所有属性名是可以的,但是变量名获取就得用另外的方法了。我们可以通过class_copyPropertyList方法获取所有的属性名称。

下面我们通过一个Person类来学习,这里的方法没有写成扩展,只是为了简化,将获取属性名的方法直接作为类的实例方法:

Objective-C版

下面主要是写如何获取类的所有属性名的方法。注意,这里的objc_property_t是一个结构体指针objc_property *,因此我们声明的properties就是二维指针。在使用完成后,我们一定要记得释放内存,否则会造成内存泄露。这里是使用的是C语言的API,因此我们也需要使用C语言的释放内存的方法free

现在,我们来测试一下,我们的方法是否正确获取到了呢?看下面的打印结果就明白了吧!

Swift版

对于Swift版,使用C语言的指针就不容易了,因为Swift希望尽可能减少C语言的指针的直接使用,因此在Swift中已经提供了相应的结构体封装了C语言的指针。但是看起来好复杂,使用起来好麻烦。看看Swift版的获取类的属性名称如何做:

关于Swift中如何C语言的指针问题,这里不细说,如果需要了解,请查阅相关文章。 测试一下是否获取正确:

获取对象的所有属性名和属性值


对于获取对象的所有属性名,在上面的-allProperties方法已经可以拿到了,但是并没有处理获取属性值,下面的方法就是可以获取属性名和属性值,将属性名作为key,属性值作为value

Objective-C版

测试一下:// 此方法返回的只有属性值不为空的属性 NSDictionary *dict = p.allPropertyNamesAndValues; for (NSString *propertyName in dict.allKeys) { NSLog(@"propertyName: %@ propertyValue: %@", propertyName, dict[propertyName]); }

看下打印结果,属性值为空的属性并没有打印出来,因此字典的key对应的value不能为nil:

Swift版

测试一下:

打印结果与上面的一样,由于array属性的值为nil,因此不会处理。

获取对象的所有方法名


通过class_copyMethodList方法就可以获取所有的方法。

Objective-C版


测试一下:

调用打印结果如下,为什么参数个数看起来不匹配呢?比如-allProperties方法,其参数个数为0才对,但是打印结果为2。根据打印结果可知,无参数时,值就已经是2了。:

Swift版

测试一下调用:

打印结果与上面的Objective-C版的一样。

获取对象的成员变量名称


要获取对象的成员变量,可以通过class_copyIvarList方法来获取,通过ivar_getName来获取成员变量的名称。对于属性,会自动生成一个成员变量。

Objective-C版

测试一下:

打印结果说明属性也会自动生成一个成员变量:

Swift版

Swift的成员变量名与属性名是一样的,不会生成下划线的成员变量名,这一点与Oc是有区别的。

测试一下:

打印结果,说明Swift的属性不会自动加下划线,属性名就是变量名:

运行时发消息


iOS中,可以在运行时发送消息,让接收消息者执行对应的动作。可以使用objc_msgSend方法,发送消息。

Objective-C版

这样就相当于手动调用[p allMethods];。但是编译器会抱错,问题提示期望的参数为0,但是实际上有两个参数。解决办法是,关闭严格检查:

image

Swift版

很抱歉,似乎在Swift中已经没有这种写法了。如果有,请告诉我。

Category扩展属性


iOS的category是不能扩展存储属性的,但是我们可以通过运行时关联来扩展“属性”。

Objective-C版

假设扩展下面的“属性”:

在实现文件中,我们用一个静态变量作为key:

其实就是通过objc_getAssociatedObject取得关联的值,通过objc_setAssociatedObject设置关联。

Swift版

Swift版的要想扩展闭包,就比OC版的要复杂得多了。这里只是例子,写了一个简单的存储属性扩展。

总结

在开发中,我们比较常用的是使用关联属性的方式来扩展我们的“属性”,以便在开发中简单代码。我们在开发中使用关联属性扩展所有响应事件、将代理转换成block版等。比如,我们可以将所有继承于UIControl的控件,都拥有block版的点击响应,那么我们就可以给UIControl扩展一个TouchUp、TouchDown、TouchOut的block等。

对于动态获取属性的名称、属性值使用较多的地方一般是在使用第三方库中,比如MJExtension等。这些三方库都是通过这种方式将Model转换成字典,或者将字典转换成Model。

写在最后

如果文章中出现有疑问的地方,请在评论中评论,笔者会在第一时间回复您的!

阅读容易,写文章难!且看且珍惜!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值