Objective-C之方法调用机制(消息传递)

原创 2015年11月19日 13:51:39

在 Objective-C之Meta-class和isa指针 中我提到,当一个对象调用方法的时候,objective-c的运行时会去这个对象的isa指针缩指向的Class的方法列表中去寻找对应的方法。我们再看一下objc_Class的定义,这次我加上注释。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE; //指向父类的指针
    const char *name                                         OBJC2_UNAVAILABLE; //类名
    long version                                             OBJC2_UNAVAILABLE; //版本
    long info                                                OBJC2_UNAVAILABLE; //类信息
    long instance_size                                       OBJC2_UNAVAILABLE; //实例大小
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE; //实例参数列表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE; //方法链表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE; //方法缓存
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; //协议链表
#endif
    
} OBJC2_UNAVAILABLE;
我们知道,对象接受到消息的时候会去methodList中寻找可以调用的方法,可以推测,methodLists里面存储的对象至少要具备两个元素,一个代表方法名,一个代表方法的具体实现,即指向具体实现该方法的函数的函数指针。
1.什么是IMP SEL Method
我们看一下objc_method_list类型链表的实现
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;


可以看到methodLists链表中以objc_method类型存储方法,再看一下Method的定义
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

可以清楚地看到,Method类型的结构体,包含一个代表方法名的SEL,一个代表方法参数类型的char*,一个指向具体实现函数的IMP指针。
什么是SEL:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
他是一个指向objc_selector类型的指针,不同的类可以拥有相同的selector, 同样地方法名对应同一个selector。当我们内存中的对象受到消息时,会到自己的方法链表中根据selector找到具体的实现函数IMP,最终执行。这是一个动态绑定的过程,在编译的时候我们不知道最终会执行哪些代码。
什么是IMP:
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif
结合之前的结论,很明显:IMP是一个函数指针,这个函数包含一个接受消息的对象id,调用方法的选标SEL,以及不定个数的参数,并返回一个id类型的返回值。我们其实可以直接调用这个函数指针,稍后我用一段测试代码来展示如何调用函数指针。

2.什么是objc_msgSend
通过之前的分析,我们大概知道,在一个方法调用的时候,我们的编译器会将它转换为对IMP的调用,这个寻找的过程是通过在对象的Class的methodLists中通过SEL进行的,那么具体的实现代码是怎样的呢。
先写一个简单的方法调用:
[self class];
在命令行中用clang重写:
liweipengdeMacBook-Air:TestIMP liweipeng$ clang -rewrite-objc testObj.m
在重写后的文件中找到方法的实现:
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"));
很明显,self指针调用的方法,转换成了objc_msgSend的函数调用,我们看一下objc_msgSend的定义:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
我们可以看到,这个函数需要的参数,一个是receiver, 一个是sel,还有函数调用所需要的参数。我们现在已经知道我们的方法调用是通过向一个函数传递调用的对象和SEL来找到对硬的IMP,但是寻找的过程是怎样的呢,我们看一下苹果官方公开的源码实现:
static Method look_up_method(Class cls, SEL sel, BOOL withCache, BOOL withResolver)
{
    Method meth = NULL;

    if (withCache) {
        meth = _cache_getMethod(cls, sel, &_objc_msgForward_internal);
        if (meth == (Method)1) {
            // Cache contains forward:: . Stop searching.
            return NULL;
        }
    }

    if (!meth) meth = _class_getMethod(cls, sel);

    if (!meth  &&  withResolver) meth = _class_resolveMethod(cls, sel);

    return meth;
}
通过分析上面的代码,并且结合objc_class的定义,我们可以知道查找过程 是这样的:
1.首先到该类的方法cache中去寻找,如果找到了,返回改函数
2.如果没有找到就到该类的方法列表中查找,如果找到了,将该IMP返回并且将它加入到cache中缓存起来,这样可以节省再次调用的开销。
3.如果还没有找到,通过该类结构中的super_class指针去它的弗雷的方法列表中寻找,如果找到了了对应的IMP,返回IMP并加入cache
4.如果在自身及父类的方法列表中都没有找到,则看是否可以动态决议(本文先不讲)
5.如果动态方法决议没有解决,进入消息转发流程(本文先不讲)

3.验证几个问题
我们先建两个基础类:
@interface TestFather : NSObject
- (int)testMethod:(int)pa1 andPa2:(int)pa2;
@end

@implementation TestFather
- (int)testMethod:(int)pa1 andPa2:(int)pa2
{
    NSLog(@"Father pa1:%d pa2:%d", pa1, pa2);
    return 0;
}
@end

@interface TestSon : TestFather
@end

@implementation TestSon
- (int)testMethod:(int)pa1 andPa2:(int)pa2
{
    NSLog(@"Son pa1:%d pa2:%d", pa1, pa2);
    return 0;
}
@end

(1)SEL是否只和方法名有关
 TestFather *father = [[TestFather alloc] init];
 TestSon *son = [[TestSon alloc] init];
        
 SEL sel = @selector(testMethod:andPa2:);
 SEL sel1 = NSSelectorFromString(@"testMethod:andPa2:");
        
 IMP imp1 = [father methodForSelector:sel];
 IMP imp2 = [son methodForSelector:sel];
        
 NSLog(@"sel -> %p  sel1 -> %p", sel, sel1);
 NSLog(@"imp1 -> %p  imp2 -> %p", imp1, imp2);
log结果:
2015-11-19 13:32:35.715 TestIMP[15252:493881] sel -> 0x100003bad  sel1 -> 0x100003bad
2015-11-19 13:32:36.398 TestIMP[15252:493881] imp1 -> 0x100000e70  imp2 -> 0x100000eb0
结论:相同名字的SEL指向同一块内存地址,不通的类可以拥有相同的SEL, 根据同一个SEL找到的对应的IMP是不同的。
(2)如何直接调用函数指针
  int (*doTestMethod)(id ,SEL , int, int);        
  doTestMethod = (int(*)(id, SEL, int, int))imp2;
  doTestMethod(son, sel, 3, 4);
(3)直接调用函数会节省方法调用时消息传递的开销,那么直接调用函数是否比oc本身的消息传递效率更高?
  long j = 1000;
  NSDate *date = [NSDate date];
  for(int i = 0; i < j; i++ )
  {
      doTestMethod(son, sel, 3, 4);
  }
  NSDate *date1 = [NSDate date];
  double time = [date1 timeIntervalSinceDate:date];
  NSLog(@"直接调用函数指针消耗时间: %f", time);
        
  date = [NSDate date];
  for(int i = 0; i < j; i++ )
  {
      [son testMethod:3 andPa2:4];
  }
  date1 = [NSDate date];
  time = [date1 timeIntervalSinceDate:date];
  NSLog(@"消息发送调用消耗时间: %f", time);
 j = 1000时,log:
2015-11-19 13:46:13.528 TestIMP[15441:505641] 直接调用函数指针消耗时间: 0.000026
2015-11-19 13:46:13.530 TestIMP[15441:505641] 消息发送调用消耗时间: 0.000017
j = 1000000时,log:
2015-11-19 13:47:20.208 TestIMP[15471:506875] 直接调用函数指针消耗时间: 0.005341
2015-11-19 13:47:20.217 TestIMP[15471:506875] 消息发送调用消耗时间: 0.007045
j = 1000000000时, log:
2015-11-19 13:49:08.250 TestIMP[15505:509402] 直接调用函数指针消耗时间: 5.165577
2015-11-19 13:49:15.282 TestIMP[15505:509402] 消息发送调用消耗时间: 7.030475
上述的测试结果跟设备有关,而且每一次的结果也不同。
结论:在调用次数少于10000次时,直接调用函数指针的效率优势并不明显,有时候还是oc的消息传递效率更好,只有循环次数非常大的时候,直接调用函数指针才有效率优势。




相关文章推荐

IOS开发笔记11-Object-C中的传递消息

转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/53513004 本文出自:【顾林海的博客】 前言在前一篇例子...

Objective-C方法的调用流程详解

在Objective-C中,方法会在运行时转换成一个消息函数的的调用,即objc_msgSend。其基本形式是objc_msgSend(receiver, selector, arg1, arg2, ...
  • lcl130
  • lcl130
  • 2014年12月13日 18:32
  • 541

Objective-C 中的方法的调用

oc语言中采用特定的语言调用类或者实例(对象)的方法称为发送消息或者方法调用。 oc中方法的调用有两种: [类名或对象名 方法名]; [ClassOrInstance met...

Objective-C学习笔记(十九)——对象方法和类方法的相互调用

其实在OC的对象方法(减号方法)和类方法(加号方法)并不是相互独立的,它们也可以发生千丝万缕的关系,今天我们来研究下它们两者相互调用的问题。该例子还是以People类为基础。 (一)对象方法调用类方法...

如何在IOS项目中调用C的函数

苹果的开发语言Objective-C是完全兼容C语言的,所以在ios项目中调用C语言的a'h

Objective-C 函数(方法)的定义和调用

转自:http://blog.csdn.net/dirknow/article/details/6602655 函数定义和调用 Object-C的函数定义和调用都比较特别,为了让对象执行某...
  • jbhand
  • jbhand
  • 2015年01月23日 13:39
  • 1811

Object-C的函数调用机制详解--消息

本文系转载,原文出处:http://blog.csdn.net/kesalin ps:Object-C和C,C++,java的函数调用机制还是有所区别的,其完全的runtime的调用方式是其实现消息...
  • likendsl
  • likendsl
  • 2012年05月14日 18:39
  • 15155

iOS-SEL的用法和讲解

1:什么是SEL: 可理解成@selector(),方法选择器。于是乎就可说成是C中的指针函数。而在oc里不能用指针函数,所有就只能搞一个@selector这来取了。 他的结果是SEL类型,用as...

OC学习篇之---KVC和KVO操作

前一篇文章我们介绍了OC中最常用的文件操作:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那么今天来看一下OC中的一...

Objective-C的消息传递机制

http://www.keakon.net/2011/08/10/Objective-C%E7%9A%84%E6%B6%88%E6%81%AF%E4%BC%A0%E9%80%92%E6%9C%BA%E...
  • qhexin
  • qhexin
  • 2011年12月07日 09:13
  • 404
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Objective-C之方法调用机制(消息传递)
举报原因:
原因补充:

(最多只允许输入30个字)