runtime—新手必学

1.RunTime消息机制

1.消息机制是运行时里面最重要的机制,OC中任何方法的调用,本质都是发送消息。

当我们实例化这个对象时:
MyClass *object = [[MyClass alloc] init]; 就会调这个实例化方法:[object showUserName];

我们大概来看一下它的底层实现:

•在编译阶段,[object showUserName] 会被编译器转化为: objc_msgSend(object, "showUserName"),相当于一种“发消息”的行为。

• 在运行阶段,执行到上述的objc_msgSend这个函数时,函数内部会到object对应的内存地址,寻找showUserName这个方法的地址并执行。如果找不到,就会抛一个“unknown selector sent to instance”的异常。

证明过程:

在终端用命令打开此类文件所在的文件夹,继续写入命令:
clang -rewrite-objc MyClass.m(把oc代码转写成

c/c++代码,我们常用它来窥探OC的底层实现),不一会在原来的同一目录下会多出一个 MyClass.cpp 文件

双击打开,可以看到
init 方法已经被编译器转化为下面这样:

objc_msgSend 函数被定义在 objc/message.h 目录下,其函数原型是:

OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )

该函数有两个参数,一个
id 类型(消息接收对象),一个 SEL 类型(方法的selector)。

@selector (SEL):是一个SEL方法选择器。SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。

对于一个类中,每一个方法对应着一个
SEL。所以一个类中不能存在2个名称相同的方法(有歧义。。。),即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。

歧义解释:
- (void)go {} + (void)go {} 这两个方法可以共存(我们知道,这两个方法的名字都是go)。

我个人的理解是:当我们向一个对象或一个类发送消息时,
runtime都会根据方法名去这个对象所属的这个类的方法列表中查找方法,而方法列表的外层应该是一个字典,根据所传的接收消息对象不同,查找的方法列表也不同。

objc_msgSend([MyClass class], @selector(go));

objc_msgSend([[MyClass alloc] init], @selector(go));

Id :是一个结构体指针类型,它可以指向 Objective-C 中的任何对象。

Class vclass = NSClassFromString(@"ViewController");

id vc = [[vclass alloc] init];

objc_object 结构体定义如下:

struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};

这就是我们通常所说的对象,这个结构体只有一个成员变量
isa,对象可以通过 isa 指针找到其所属的类。isa 是一个 Class 类型的成员变量,那么 Class 又是什么呢?如下:

•·Class 也有一个 isa 指针,指向其所属的元类(meta)。

•·super_class:指向其超类。

•·name:是类名。

•·version:是类的版本信息。

•·info:是类的详情。

•·instance_size:是该类的实例对象的大小。

•·ivars:指向该类的成员变量列表。

•·methodLists:指向该类的实例方法列表,它将方法选择器和方法实现地址联系起来。methodLists 是指向 ·objc_method_list 指针的指针,也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因。

•·cache:Runtime 系统会把被调用的方法存到 cache 中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。

•·protocols:指向该类的协议列表。

经过以上的讲述,我们大概可以了解到,当调用一个方法时,其运行过程大致如下:

•底层调用 [p performSelector:@selector(eat)] 方法,编译器在将代码转化为 objc_msgSend(p, @selector(eat))把方法的调用者和方法选择器当做参数传递过去。

•首先,检测这个 selector 的 target 是不是 nil,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。

•如果target != nil,方法的调用者会通过 isa 指针来找到其所属的类,然后在 cache 中查找该方法,找得到就跳到对应的方法去执行。

•若 cache 中未找到,再去 methodList 中查找。若能找到,则将 method 加入到 cache 中,以方便下次查找,并通过 method 中的函数指针跳转到对应的函数中去执行。

•若 methodlist 中未找到,则去 superClass 中查找,一直找到 NSObject 类为止。若能找到,则将 method 加入到 cache 中。

•如果还是查不到,则会执行消息转发,抛出异常。

我们来看看底层调用方法的几个实例(分别是无参无返回,有参无返回,有参有返回)

2.RunTime交换方法

应用场景:当系统自带的方法功能不够,需要给系统自带的方法扩展一些功能时。

eg:实现image添加图片的时候,自动判断image是否为空,如果为空则提醒图片不存在。

有以下三种比较好的解决方法
:

1.自定义类, 重写系统自带的imageName:方法,这种方法虽然可以实现,但是它的弊端就是必须要使用自己的类,依赖性强。

2.给UIImage添加一个分类, 改变系统类的实现,给系统的类添加方法的时候调用(每次使用都需要导入头文件,并且如果项目比较大,之前使用的方法全部需要更改)。

3.使用runtime的交互方法,给系统的方法添加功能. 具体实现 : 添加一个分类 --> 在分类中提供一个自定义判空增加新功能的方法 --> 将这个方法的实现和系统自带的方法的实现交互.

交换方法的本质其实是交换两个方法的实现,即调换
le_imageNamed:和imageName:方法,达到调用le_imageNamed:其实就是调用imageNamed:方法的目的。

那么首先需要明白方法在哪里交换,因为以后都是使用自己定义的方法取代系统的方法,所以,当程序一启动,就要求能使用自己定义的功能方法。我们一般在
+ (void)load 方法里实现交换方法 (当程序启动的时候就会调用该方法,换句话说,只要程序一启动就会调用load方法,整个程序运行中只会调用一次)

•注意:交换方法时 le_imageNamed:方法中就不能再调用imageNamed:方法了,因为调用imageNamed:方法实质上相当于调用 le_imageNamed:方法,会循环引用造成死循环。

想要学习前端开发的同学,可以加群:
543627393 学习哦!


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值