Objective-c方法调用流程

简介: Objective-c方法调用流程   Objective-c是一门动态语言,动态两个字主要就体现在我们调用方法的时候,运行时回动态的查找方法,然后调用相应的函数地址。运行时是整个Objective-c程序的基石,有了它我们的程序才能正常运行起来。

Objective-c方法调用流程

  Objective-c是一门动态语言,动态两个字主要就体现在我们调用方法的时候,运行时回动态的查找方法,然后调用相应的函数地址。运行时是整个Objective-c程序的基石,有了它我们的程序才能正常运行起来。

  NSObject是Cocoa中绝大部分类的基类,它主要是提供了序列话,拷贝对象,以及支持运行时动态识别的框架。

  在Objective-c中每一个类对象最开始的位置都会有一个isa指针,该指针指向一块内存区域,该部分主要包含两部分信息:

  1、指向父类的指针。

  2、自身的方法分发表。

  有了这两部分,Objective-c的方法的调用流程就可以跑起来了。当我们调用一个对象的某一个方法的时候,首先会在当前类的分发表中寻找该方法,如果找不到对应的方法,然后再去其父类中寻找该方法,依次类推直到找到对应的方法为止,流程图如下:

  你可能会想到,如果一个类有很深的继承层次,每次去调用根类的某个函数,岂不是都要做很多次查找。理论上是这个样子的,不过runtime也并非那么傻,它会为每一个类(不是对象)维护一个经常调用的方法的列表,只要调用过就会缓存起来(官方没有明确说明缓存机制),这样当程序运行稳定以后整个方法调用的过程就会更加高效。

  通过学习官方文档Objective-C Runtime Programming Guide,可以发现其实所有的selector调用最后都会转化为C类型的函数调用。举个例子我们创建了一个A类型的对象aSample,然后调用其test方法([aSample test]),编译的时候,编译器就会将该调用转化为objc_send(aSample, selector)的形式,runtime会调用test方法实现所对应的函数地址。该函数的参数包含了两个隐含的参数self以及_cmd,其中self指向调用该方法的对象,_cmd则代表要调用的方法。

  前面提到了NSObject提供了很多遍历的方法可以和运行时进行交互,其中有个方法methodForSelector,通过它我们可以直接获取到指定的方法对应的函数指针。通常我们直接使用Objective-c方式的方法调用就可以了,但有时程序中可能会频繁的调用某一个方法,为了提高效率。我们可以直接获取到方法对应的函数地址,然后直接调用该函数,这样就少了动态识别的时间。

  下面举个例子:

// 父类中定义该方法
- (void)testMethod
{
    //NSLog(@"the implementation of BaseSample!!!");
    int a = 5 / 2.0f;
    a = ~a;
}

// 测试方法,分别使用两种方法调用1亿次
- (void)test
{
    void (*methodAddress)(id,SEL);
    methodAddress = (void(*)(id,SEL))[self methodForSelector:@selector(testMethod)];
    
    NSLog(@"Invoke with Method Address start!!!");
    for (int i = 0; i < 100000000; ++i) {
        methodAddress(self, @selector(testMethod));
    }
    NSLog(@"Invoke with Method Address finish!!!");
    
    NSLog(@"Invoke with direct selector start!!!");
    for (int i = 0; i < 100000000; ++i) {
        [self testMethod];
    }
    NSLog(@"Invoke with direct selector finish!!!");
}

  运行结果如下图:

  可以看出调用时间:使用函数地址调用共花费0.151s,直接调用方法花费0.734s。时间是有一点儿差距,但是已经微乎其微了,这也从侧面说明了runtime的缓存机制还是很给力的。

  当我们调用某一个不存在的方法的时候,程序会crush,在命令行提示“unrecognized selector sent to instance 0xxxxxxx”,并抛出“NSInvalidArgumentException”的异常。当调用一个对象不能识别的方法时,runtime会一直沿着类的继承关系往基类方向寻找,直到NSObject类,如果还是识别不了该方法的话,再抛出异常之前runtime还给我们了最后一次“补救”的机会。它会先调用forwardInvocation方法,如果我们想把这个方法异常调用捕获并传递到其他地方的话,可以在类中重写该方法。NSObject对于forwardInvocation方法的默认实现是调用doesNotRecognizeSelector方法,而doesNotRecognizeSelector则是直接抛出异常。

  当调用forwardInvocation的时候会传入一个NSInvocation的参数,该参数标识了调用的方法的对象以及调用的方法,并对该方法的调用结果进行封装。我们重写forwardInvocation方法的时候,还必须同时重写methodSignatureForSelector方法,该方法返回表示一个方法的字符串,具体如何构建请看Type Encodings

  下面举一个简单的重写forwardInvocation的例子:

#pragma mark-
#pragma mark 重写 ForwardInvocation

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    }
    else {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"Hello unreconginized selector!");
}

// 在init中调用一个不存在的方法hello
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self hello];
    }
    return self;
}

  上面的例子,截获了不能识别的方法调用,创建了一个返回void类型的方法签名,当调用不能识别的方法的时候打印简单的日志。当然在程序中最好不要这么做,特别是开发的时候,大部分时候我们更希望能够尽早的发现这种调用错误。

  总结:Objective-c方法调用的流程就基本介绍完了,有什么写的不对的地方欢迎指正。

注:本文欢迎转载,转载请注明出处。同时欢迎加我qq,期待与你一起探讨更多问题。


部门招人: 高级iOS、Android、前端开发,有意私聊,博主请你喝️
如果觉得本文帮到了你,记得点赞哦,当然也可以请博主喝一杯豆浆
o_IMG_2722.PNG o_qq.PNG
微信二维码 QQ二维码
目录
相关文章
|
C# iOS开发 Java
****Objective-C 中的方法的调用
oc语言中采用特定的语言调用类或者实例(对象)的方法称为发送消息或者方法调用。 oc中方法的调用有两种:  第一种: [类名或对象名 方法名];   [ClassOrInstance method]; [ClassOrInstance method:arg1]; ...
1023 0
|
iOS开发
objective-c 方法加号(+) 减号(-)
至于你说的用加号声明一个成员变量完全就是错误的理解。要把成员变量声明为静态的,必须使用static关键字。 本文转自博客园知识天地的博客,原文链接:objective-c 方法加号(+) 减号(-),如需转载请自行联系原博主。
2523 0
|
Java iOS开发
Objective-C方法与函数的区别
Objective-C方法与函数的区别 方法是唯对象所有 函数是不依赖于对象存在的 方法 函数 - (void)test; void test(); 方法是以减号 ...
955 0
|
JavaScript 前端开发 iOS开发
Javascript与Objective-C 字符串与数组的方法类比
table {border-collapse:collapse;} table td {border:1px solid #ccc;} String vs NSString JavaScript string ...
656 0
|
iOS开发
Objective-C中的NSObject对象经常使用到的方法
<span style="font-family:'Courier New'; margin:0px; padding:0px; line-height:1.5; color:rgb(0,128,0)">/*</span><span style="font-family:'Courier New'; margin:0px; padding:0px; line-height:1.5; col
904 0
|
Android开发 iOS开发
利用Objective-C运行时hook函数的三种方法
方法一,hook已有公开头文件的类: 首先写一个Utility函数: #import void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL) { Method oldMethod ...
1194 0
|
iOS开发
Objective-C的方法替换
Objective-C的方法替换 (Method Replacement for Fun and Profit) 本文将要讨论Objective-C中的方法替换(method replacement)和swizzling(移魂大法)。
873 0