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




objc直接通过指针访问对象实例变量

我们现在来做一件被认为是very bad的事情,如题所示;无论实例变量是私有的、保护的都可以通过地址访问到,并且还可以修改之。这可以称之为所谓的“超级键值编码”。首先上代码:#import @inte...
  • mydo
  • mydo
  • 2015年06月30日 17:29
  • 1020

Java方法调用绑定

将一个方法调用同一个方法主题关联起来称作绑定. 若在程序执行前进行绑定叫做前期绑定,C语言只有一种绑定方式就是前期绑定. 后期绑定就是根据运行时对象的类型进行绑定.后期绑定也称为动态绑定或者运行时...
  • Kevin_Samuel
  • Kevin_Samuel
  • 2015年02月17日 00:00
  • 621

Objective-C 消息传递机制详解

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

Android四种常用的消息传递机制/模式的比较

四种分别是callback interface,handler-message,broadcast receiver和observer-subject。
  • zy13608089849
  • zy13608089849
  • 2017年07月07日 18:51
  • 322

解析Android的消息传递机制Handler

1. 什么是Handler: Handler 网络释义“操纵者,管理者的”意思,在Android里面用于管理多线程对UI的操作; 2. 为什么会出现Handler: 在Android的设...
  • kekeleqy
  • kekeleqy
  • 2016年08月18日 11:10
  • 748

Objective-C的消息传递机制

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

Android消息传递机制浅析

1.Looper、Handler、MessageQueue的关系 Looper 用于线程的消息循环,一个线程只能有一个Looper对象 Handler 执行任务调度和发生一些操作(在未来某时刻) 执...
  • qq_21430549
  • qq_21430549
  • 2016年05月03日 20:31
  • 2153

OC的消息传递机制

我们都知道oc是一门动态语言,dia
  • hmxhh
  • hmxhh
  • 2014年05月08日 11:25
  • 1391

理解消息传递机制和消息转发机制

消息传递机制在对象上传递方法叫做“传递消息”(pass a message)。消息有“名称”(name)或“选择子”(selector),可以接受参数,而且可能还有返回值。 在Object-c中,如...
  • qq_33351410
  • qq_33351410
  • 2016年09月27日 11:07
  • 772

消息传递机制的具体实现过程(分析源码之后的总结)

Android中的Handler的机制与用法详解,什么是Handler,如何传递 Message,传递 Runnable 对象,传递 Callback 对象,Handler 原理是什么?Handler...
  • zy_style
  • zy_style
  • 2016年09月19日 20:24
  • 318
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Objective-C之方法调用机制(消息传递)
举报原因:
原因补充:

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