事岀无常必有妖-iOS捉妖记之(Runtime)

111644823-5eb0eb67175ba6af

捉妖记.jpg

写在前面

看完本篇之后你将获得:

  • 了解什么是runtime
  • 知道可以利用runtime做到哪些事情
  • 掌握用runtime开发的常用方法

Runtime是开源的,任何时候你都可以从http://opensource.apple.com获取。事实上查看 Objective-C 源码是我理解它是如何工作的第一种方式,在某些问题上要比读苹果的文档要好。你也可以下载笔者在写这篇文章时最新objc4-680.tar.gz

引言

相信很多从事iOS开发的小伙伴们都听过这样一句形容runtime的话:

runtime就像是iOS开发中的妖怪,谁都听说过,但少有人见(用)到过!

这句话是某知名培训机构内某老师对学生们说的一句话(原谅我每年都会去down培训视频大致的过一遍),相信不少人尤其是初学的萌新们还没了解过runtime,听了这句话就被吓到了!直接在心里给runtime打个一打标签[危险,慎用,底层,难,用不到,不用掌握]。以至于很多人做了有一段时间的iOS开发却依然对其一知半解……

定义

Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体。

其实就在下个人的理解:runtime就是丫Objective-C 的灵魂!Objective-C之所以叫Objective-C是因为他比C语言不同,是面向对象的。但是Objective-C为什么有面相对象的能力?就是因为有runtime这个鬼东西!

进阶

我们为什么要学习runtime?

  • runtime可以遍历对象的属性
  • runtime可以动态添加/修改属性,动态添加/修改/替换方法,动态添加/修改/替换协议
  • runtime可以动态创建类/对象/协议等等
  • runtime可以方法拦截调用

其实runtime所能做的还不止这些,你甚至可以利用它来把一个Class A的实例对象a在程序中当作Class B的实例对象来用。所以很多iOS开发者把runtime叫做obj-C的黑魔法!

常用方法

先来个最简单最基本的也是几乎所有runtime文必备的例子:

obj-C: [obj func];
runtime:objc_msgSend(obj, @selector(func);

很多初学者除了知道runtime把对象的方法调用转化成消息发送的代码之后就不知道其他的了,但是显然仅仅知道上述的转化并没有什么“吡-”用,我们来看runtime中比较常用(实用)的几种基本用法:

  • 遍历对象的属性
    首先定义一个简单的类Person

    然后在需要遍历对象的属性时

    这时就会打印出这个类对象的属性相关信息:

    name:T@”NSString”,C,N,V_name
    age:Tq,N,V_age

  • 消息转发

其实讲道理的话,消息转发不是三言两语就可以讲清的,我只能在这里粗浅的介绍一下,让大家会用而已。
周末可能会写一篇详解runtime的文章来细致的介绍runtime中的术语以及runtime消息发送机制,动态方法解析,重定向以及消息转发。包括我们熟用但是可能不知道其源码是什么样的id,SEL,objc_object,objc_class以及其结构也会详细的讲解到,感兴趣的小伙伴可以关注我,这样我的新文章会第一时间推送给你。

我们这里的[消息转发]指的就是我上面提到的动态方法解析,重定向以及消息转发,我们先来看一张图:

121644823-e59e8c51c0eb393e

消息转发流程.png

动态方法解析:

从上图可以知道,当对一个实例对象obj发送一条消息func[obj func],当前obj如果没有对func实现对应的方法,那么就runtime会调用+ (BOOL)resolveInstanceMethod:(SEL)sel方法允许开发者对当前受到的消息func做出响应,这就是动态方法解析

继续拿上面的Person举例子,给Person类加一个体重weight属性

然后在.m文件中加入一下代码

然后可以在代码就调用Person的setWeight方法

这时候如果不重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法本应该异常的,但是你可以发现程序会打印出信息:

Dynamic setWeight

重定向:

那么还是看图说话,如果没有重写+ (BOOL)resolveInstanceMethod:(SEL)sel方法,那就就会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,把这个消息让另一个对象来处理,这次叫做重定向

跟着上面的例子走,先另一个类People用来等待重定向:

给新写的People类加一个weight方法,但是注意:People没有weight属性!

接下来我们重写- (id)forwardingTargetForSelector:(SEL)aSelector方法:

然后我们在刚才的执行代码中:

然后运行,经历过上面的例子你肯定知道不会异常啦,而且你会发现虽然你给weight属性赋值明明是75,可是打印结果是:weight = 70。这就是Person类- (id)forwardingTargetForSelector:(SEL)aSelector方法中把这条信息抛给了people对象,调用了People类的weight方法!

消息转发

那么如果上面的两个方法都没有重写,并且消息依然是当前对象没有实现的方法,runtime才会启用消息转发调用– (void)forwardInvocation:(NSInvocation *)anInvocation,需要注意的是很多文章没有提到这个方法花费代价较大,如果要实现把消息转发类似的功能建议最好使用重定向,而且再调用这个方法前runtime会先调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法。

我们跟着上面的例子,继续给Person类加入属性:

以及上面提到的两个方法:

别忘记了在People类中添加对应的方法:

最后,我门只需要在执行代码块中加入代码:

结果显而易见,相信各位都知道将会打印信息:

People setID: xxxx

写在最后

其实runtime就是我们无时无刻不在用的东西,只是人们习惯对看不到的东西怀有恐惧心理而已。我们平时的obj-C代码都是被runtime转译为c和汇编语言运行的。我个人认为大公司为什么喜欢在面试时问runtime相关的东西是因为大公司往往不仅仅要会干活的人,它还会要求这些会干活的人知道其中的原理!我们自己也应该要求自己或多或少的理解这些原理,知道我们为什么写出的obj-C代码经历了哪些过程run到我们的设备上,不要敲了很多年的代码还是一只只会干活的码农。

1 2 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部