Objective-C简介

Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C的原意就是在C语言主体上加入面向对象的特性

ObjC是通过C语言模拟了Smalltalk的消息传递机制来实现面向对象的特性的;因此其他所有非面向对象的语法都与C语言中一致。

消息机制(Runtime)

ObjC的最大特点就是承自SmallTalk的消息传递机制。与其他主流面向对象语言风格不同,在ObjC中调用对象的方法实质上是向对象绑定的函数传递消息
主流面向对象语言中类别与方法的关系清晰严格,一个方法必定属于一个类,在编译时就已经绑定,不可能调用一个类别中不存在的方法。在ObjC中,类别与函数的关系比较松散,它们之间仅仅通过一个类别中存储的函数表来构建联系,当调用某个方法时,类别的实例对象将消息发送到方法;所有的方法都被视为对消息的回应,而消息的处理则会在执行时才会动态的决定(Runtime)。

Runtime是一个C语言和汇编混合实现的库,它是ObjC语言的核心和动力。Runtime通过C语言的结构体定义了ObjC中类、对象、方法、函数、协议等数据结构,同时实现了它们之间的消息传递机制

消息传递

通过Runtime发送一个消息后,会找到接收该消息的对象实例并将消息传递给它。
一个对象在接收到消息之后,有三种可能的处理方式:一、回应该消息并执行方法;二、若无法回应,则可以将消息转发给其他对象;三、若前两个途径均不能处理该消息,则使用处理意外的方法处理该消息–默认是抛出错误;因此如果对象中接收到了一个无法处理的消息,也只会在执行期抛出异常,不会出现错误或崩溃;这一特性使得ObjC天生具有鸭子类型的动态绑定能力,也使得ObjC成为一门动态语言。

消息发送

ObjC中所有消息都是通过Runtime中的objc_msgSend(receiver, selector, arg1, arg2, ...)方法发送的;该方法的第一个参数receiver是接收消息的对象,第二个参数selector是调用的函数签名,剩下的则是传递的函数参数

这个消息函数只有编译器能调用,不能手动调用

当调用一个ObjC方法时,并没有显示的指定receiverselector,这两个参数是Runtime自动添加的,receiver是该方法所属的对象,而selector则是ObjC方法对应的C函数的签名

每一个ObjC方法实质上都是一个至少带有receiver(接受对象)和_cmd(执行的selector)的C语言函数,ObjC方法和C函数的对应关系存储在类的一个函数表中

在被调用的方法实现中,可以获取这两个参数信息

  • getTheReceiver() 方法可以获取接收对象,一般是 self
  • getTheMethod() 方法可以获取 selector

消息接收

消息通过objc_msgSend函数发出之后,首先经历的是查找的过程。

  1. 根据 receiver 对象的isa指针找到该对象实现的类对象

    对象结构体中只有一个指向类结构体的isa指针

  2. 查询类对象中方法函数列表 methodlists,通过函数签名结构体SEL查找对应的实现结构体Method;如果在当前类中找不到会沿着继承链向上查找

    在消息的接收过程中,类结构体中的几个元素至关重要

    • isa 指针:用于确定消息接收者实例对象
    • super_class 指针:用于找不到对应的函数签名时继续沿父类查找,直到根对象
    • methodList 类调度表(dispatch table):保存了自身持有的所有方法的散列表(方法名和地址),可以通过 Selector 中的方法名找到对应的方法地址
    • cache 方法列表:为了提高效率,Runtime 会缓存调用过的 Selector 和方法地址,在调度表methodList中查找之前会先去cache中找
  3. 得到函数结构体Method后,可以确定函数实现指针IMP和对应的参数,调用该函数

提高调用效率

动态特性使得每次调用方法都会经历一系列的查找,虽有会有缓存,但是还是要经历消息的发送接收查找过程,只是减少了查找的次数。因此如果需要连续的调用一个方法多次,可以通过获取方法地址直接调用的方式来提高速度

methodForSelector: 方法可以获取对应 Selector 的地址(指针);返回的指针是 Void *,需要转换为对应的函数类型后才能使用。eg.

1
2
3
4
5
6
7
8
9
10
/* 声明函数类型:void (* methodType)(id, SEL, argType, ...) 
* void 是返回值类型,此处为空
* id 和 SEL 是隐藏参数,此处需要显示传递
* argType 是函数的实际参数类型
*/
void (* test)(id, SEL, int);
// 将 void * 类型强转为对应函数类型
test = (void (*)(id, SEL, int))[self methodForSelector:@selector(print:)];
// 直接调用函数
test(self, @selector(print:), 999);

消息查找的具体实现

消息查找是通过调用类中的+ (BOOL)resolveClassMethod:(SEL)sel+ (BOOL)resolveInstanceMethod:(SEL)sel方法来分别查找类方法和对象方法

可以在这两个方法中动态的添加一些原本没有的方法实现

1
2
3
4
5
6
7
8
9
10
11
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethodIMP)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return true;
}
return [super resolveInstanceMethod:sel];
}

void dynamicMethodIMP(id receiver, SEL _cmd, ...) {
// dynamic do somthing ...
}

消息转发

在消息查找过程中,如果沿继承链最终依然没有找到对应的函数实现,消息就会进入转发流程,消息的转发分为两种情况:

  1. 当前消息所属对象能够获取待转发的对象,此时直接返回该对象完成转发,如果无法获取则进入下一步骤
  2. 当前消息所属对象不能获取待转发的对象,此时需要将消息封装为函数签名,然后通过函数签名完成转发

可获取目标对象的转发

消息转发首先检查是否有明确目标对象的转发,如果有则直接将消息发送到新的对象
该过程通过 - (id)forwardingTargetForSelector:(SEL)aSelector 方法完成,该返回的实例对象就是新的消息对象,返回 nil 则继续进入下一步的转发

无目标对象的转发(更加灵活)

如果当前消息所属对象无法获取消息转发的目标对象,则继续检查是否有对应的函数签名;如果有则通过函数签名调用将消息转发出去

  1. 尝试通过 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法获取对应的函数签名
  2. 如果获取到对应的函数签名,则通过调用 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法转发消息

可以通过类方法+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector获取类中对应的方法签名 (???消息转发过程中如果能直接获取到转发对象,是否也是通过此方法获取函数签名后完成的转发)

消息异常

如果在消息无法处理并且转发失败之后,会抛出异常

具体的实现机制是NSObject类中,- (void)forwardInvocation:(NSInvocation *)anInvocation方法默认实现是调用 doesNotRecognizeSelector: 方法抛出异常

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~