iOS-Runtime、对象模型、消息转发

时间:2021-03-07 20:40:19

Objective-C只是在C语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

Message

  执行一个方法,有些语言、编译器会执行一些额外的优化和错误检查,因为调用的关系很直接也很明显。但是对于消息分发来说,就不一定了。在发消息前不必知道某个对象是否能处理消息,你把消息发给它,它可能会处理,也可能会交给其他的objec 处理。一个消息不用对应一个方法、一个对象也可能实现一个方法来处理多条消息。

在objcetive中,消息是通过objc_msgSend()这个runtime实现的。编译器把消息的分发转变成objc_msgSend执行。

id returnValue = [someobject messagename:parameter];

其中someObject是接收者(receiver),messagename叫做Selector,Selector和参数合起来叫做消息.

objc_msgSend(id self, sel cmd,...)

第一个参数代表接收者,第二个参数是Selector,后面的参数就是消息中得参数,位置顺序不变。所以根据上面的原型,我们可以把函数改写成这样:

id returnValue = objc_msgsend(someobject,@selector(messagename:),parameter);

  objc_msgsend函数会根据接受者和selector的类型调用适当的方法。会在接收者所属的类里面搜寻“方法列表”(list of method).如果能找到与selector名称相符合的方法,就跳转至实现代码。如果找不到的话,就会向上查找,等找到合适的方法后跳转。如果还是找不到得话就会执行“消息转发”的操作。

  按照这个思路,调用一个方法需要很多步骤。但是objc_msgsend会将结果缓存到快速映射表(fast map)里面,每个类都有一个这样的缓存,若是稍后还向该类发送相同的消息的话,执行起来就很快了。

对象某型

打开NSObject.h可以看见下面object_class的组成

iOS-Runtime、对象模型、消息转发

打开runtime.h可以看见下面object_class的组成

iOS-Runtime、对象模型、消息转发

 

isa指针:每个对象都是类的实例,isa指针指向这个实例所属的类,每个类也是一个对象,类也有isa指针。

super_class:父类

name:类的名字

info:类的一些信息

instance_size:实例的大小

objc_ivar_list:实例的参数列表

objc_method_list:实例的方法列表

objc_cache:方法的缓存

objc_protocol_list:协议方法列表

 

借用网上的一张图:图片来自这里

iOS-Runtime、对象模型、消息转发

 

根据这张图片,可以发现有以下信息:

(1)类也是一个对象,这个对象是另外一个类的实例,这个类是meta(元类)

  (2) 每个meta类也是一个对象,分别指向根meta类。

(3)根元类的isa指针指向自己,形成了一个闭环。

(4)在继承关系中,由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以,为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。

Method Swizzling

Method Swizzling 可以交换两个方法的实现。为什么会有这样的功能呢?首先看看扩展类的两种途径,第一种是子类化,重写父类的方法,然后调用父类的实现。但是使用子类的过程中,如果返回的时父类的类型的话怎么办?可以使用Category,添加一个扩展方法,这个方法如果没有和系统调用的方法重名的话一般情况下是没有问题的,但是如果重写了系统的方法的话,那么就永远不能调用这个方法了。所以Method Swizzling这个方法能解决这些问题,技能扩展类,又还能调用原来类的实现,通常情况下先建立一个与系统对应的扩展类,然后通过method_exchangeImplementations方法交换它们的实现。

首先定义一个NSString的扩展类

@interface NSString (Addition)
- (NSString *)test_myLowerString;
@end
@implementation NSString (Addition)

- (NSString *)test_myLowerString
{
    NSString *lowercase = [self test_myLowerString];
    NSLog(@"%@ =>%@",self,lowercase);
    return lowercase;
}

@end

然后交换它们的实现方法

    NSString *testString = @"TEST";
    NSLog(@"%@,",[testString lowercaseString]);
    
    Method originMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method swapMethod = class_getInstanceMethod([NSString class], @selector(test_myLowerString));
    method_exchangeImplementations(originMethod, swapMethod);
    NSLog(@"%@",[testString lowercaseString]);

打印的结果是:

iOS-Runtime、对象模型、消息转发

所以我们改写了系统的lowercaseString方法,每当我们调用扩展的test_myLowerString方法时候,其实是调用系统的lowercaseString方法,这种做法一般情况用在调试系统,不过最好不建议改写系统的一些方法,可能会带来不可调试的后果,所以使用前需慎重。

动态方法处理和消息转发

上面谈了方法的交换,是对消息处理的一种,下面再谈另外一种方法的处理

iOS-Runtime、对象模型、消息转发

 

参考链接

 http://blog.devtang.com/blog/2013/10/15/objective-c-object-model/

http://limboy.me/ios/2013/08/03/dynamic-tips-and-tricks-with-objective-c.html