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的消息传递效率更好,只有循环次数非常大的时候,直接调用函数指针才有效率优势。




版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Objective-C 中的方法的调用

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

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...

Objective-C的消息传递机制

各种语言都有些传递函数的方法:C语言中可以使用函数指针,C++中有函数引用、仿函数和lambda,Objective-C里也有选择器(selector)和block。 不过由于iOS SDK中的大部...

Objective-C 消息传递机制详解

Objective-C语言中方法的传递有二种:①Selector ② Blocks,本文主要说一下Selector,关于Blocks会在后续总结一下。 消息传递模型(Message Pass...

[精通Objective-C]对象和消息传递

[精通Objective-C]对象和消息传递参考书籍:《精通Objective-C》【美】 Keith Lee目录精通Objective-C对象和消息传递 目录 对象 创建对象 初始化对象 重构Ato...

【iOS沉思录】Objective-C语言消息传递机制三道防线:消息转发机制详解

消息传递机制: 在OC中,方法的调用不再理解为对象调用其方法,而是要理解成对象接收消息,消息的发送采用‘动态绑定’机制,具体会调用哪个方法直到运行时才能确定,确定后才会去执行绑定的代码。方法的调用实际...

Object-C的消息传递机制和method swizzling方法混淆

objc_msgSend 在Object-C中,我们经常调用一个对象的方法,通常我们将这个过程成为 消息传递。不同于 C 语言对对象方法的静态调用,Object-C 是通过 Dynamic Bind...

Objective-c 方法调用&内省机制(introspection)&selector

//所有的objects都存储在heap里,因此永远使用指针来访问abjects 例:NSString *s = …; //statically typed id obj = s; ...

剖析Object-C中的属性与消息传递机制

一、属性 属性是OC的一项特性,用于封装对象中的数据,OC对象通常会吧其所需要的数据保存为各种实例变量,实例变量一般都是通过存取方法来访问的。 例如有一个Person类,该类具有姓名和年龄等属性 #...

Objective-C消息机制的原理

在Objective-C中,message与方法的真正实现是在执行阶段绑定的,而非编译阶段。编译器会将消息发送转换成对objc_msgSend方法的调用。     objc_msgSend方法含两个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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