题目

上题目,已知A是爷爷,B是爸爸,C是孙子。

@interface A : NSObject
- (void)f;
@end @interface B : A
- (void)f;
- (void)g;
@end @interface C : B
- (void)f;
@end

A,B,C都各自实现了函数f,只有B实现了函数g。

@implementation A
- (void)f
{
NSLog(@"A");
}
@end @implementation B
- (void)f
{
NSLog(@"B");
} - (void)g
{
[self f];
[super f];
NSLog(@"%@", [self class]);
NSLog(@"%@", [super class]);
}
@end @implementation C
- (void)f
{
NSLog(@"C");
}
@end

请问,下面代码输出什么?

C *c = [[C alloc] init];
[c g];

答案是CACC.

分析

1.C中没有g,为什么可以调用B的方法

C继承自B,所以C中虽然没有g方法,但是会去从下往上找,找到B类的g方法进行调用。

2.关于[self f]

调用B的g的过程中,self其实是C,因为此时函数g消息的接受者是c。所以系统从self的类(即C)开始从下往上找,因为C中实现了f,所以直接输出C。

3.关于[super f]

这个问题是最困惑我的,卧槽,居然输出的是A而不是B?!

首先要明确的一点是,self和super不是同一个层面上的东西。

self是什么?

self 是类的隐藏的参数,指向当前当前调用方法的类,另一个隐藏参数是 _cmd,代表当前类方法的 selector。

super是什么?

super可不是想象中的self.superclass啊

super是Objective-C的语法糖,是一个编译器指示符,像宏一样,运行时XCode可不认识super这个东西。

通过 clang 命令重写 Objective-C 代码

clang -rewrite-objc test.m

可以发现其实Objective-C的函数调用的本质是

id objc_msgSend(id theReceiver, SEL op, ...)

省略号表示不定参数。

即[self f]的本质是 objc_msgSend(self, @selector(f), "");

所以,这点跟C++不同,并不是self去调用了函数f,而是消息f发送给了self,self是消息接受者。正是因为这样,Objective-C中[nil f]这种写法没问题,因为发送给nil的任何消息都返回nil。

objc_msgSend(theReceiver, op, ...)的本质是,从theReceiver所在的类开始找,不断往上找,看看哪个类实现了op,一旦找到则将消息发送给它处理。

但是[super f] 可不就是这样了,[super f]本质是

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

省略号表示不定参数。

即[super f]的本质是 objc_msgSendSuper(super, @selector(f));

objc_super何许人也?

struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};

objc_super的receiver是什么?

reveiver是当前消息的接受者,代码里写的是[c g],所以super的receiver就是C.你也可以理解为就是self。

objc_super的super_class是什么?

super_class是当前super所在代码的类的父类,很绕口,但是这句一定要好好理解。比如无论谁调用了g,是C也好是B也罢,g中[super f]的super的super_class一定是A,因为super这5个字母是写在B里面的。

objc_msgSendSuper(super, op, ...)的本质是,从objc_super->super_class所在的类开始找,不断往上找,看看哪个类实现了op,一旦找到了则将消息发送给它处理。

在编译的时候,编译器看到了super,会因此构造一个struct。objc_super->receiver填了C,objc_super->super_class填了A。因此运行时编译器变不认识了super。

如果你已经清楚了上面的逻辑,请回头看看[super f]。

[super f],super->reveiver是C,super->super_class是A。所以从A开始找f,找到了则系统可能这样调用

objc_msgSend(super->recevier, @selector(f))  //此时f是已经找到了的f,即A的f

4.关于[self class]

首先非常有必要看看class的实现,参考自苹果RunTime开源代码

- (Class)class {
return object_getClass(self);
}

可以看到class返回的是self的类名。

好,继续,[self class]中,class也是一个消息,接受者是self,也就是C,所以系统从C开始找class函数的实现,没找到,找B,找A都找不到,到了NSObjcect这一层找到了。

此时调用了NSObject的class方法,那此时self是C,那就返回了C。

5.关于[super class]

这个问题一开始看起来也很奇怪,居然[self class]和[super class]输出了同一个东西,这不科学啊。但是把上面的知识理解好了,也就不奇怪了。

执行[super class]时,编译器将[super class]替换为

objc_msgSendSuper(super, @selector(class))

其中super为reveiver=C,superclass=A。

系统从A开始找class,找不到,到了NSObject这一层找到了 class 方法

objc_msgSend(super->receiver, @selector(class))

此时调用了NSObject的 class 方法,那此时self是C,那就返回了C

 - (Class)class
{
return object_getClass(self);
}

不仅仅是 [super class],其他诸如 [super init] 的方法,都是同一个流程:

1. 从 super->super_class 开始查找 IMP

2. 执行 objc_msgSend(super->receiver, SEL)

注意最终进行 objc_msgSend 的第一个参数始终是 super->receiver,而不是 super->super_class

总结

道理懂了之后就是思考的效率问题了,凡是self的就从消息接受者的类开始从下往上找;凡是super的就从写super的这个类开始往上找。

参考链接

刨根问底Objective-C Runtime(1)- Self & Super

Objective-C 中Self 和 Super 详解

苹果RunTime开源代码

Objective-C Super的理解

Objective-C Runtime初探:self super的更多相关文章

  1. 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

  2. Objective C Runtime 开发介绍

    简介 Objective c 语言尽可能的把决定从编译推迟到链接到运行时.只要可能,它就会动态的处理事情.这就意味着它不仅仅需要一个编译器,也需要一个运行时系统来执行变异好的代码.运行时系统就好像是O ...

  3. iOS动态性 运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)

    借助前辈的力量综合一下资料. OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是 ...

  4. 刨根问底Objective-C Runtime(4)- 成员变量与属性

    http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...

  5. Objective-C Runtime(一)预备知识

    很早就知道了Objective-C Runtime这个概念,「Objective-C奇技淫巧」「iOS黑魔法」各种看起来很屌的主题中总会有它的身影:但一直没有深入去学习,一来觉得目前在实际项目中还没有 ...

  6. iOS runtime整理

    iOS利用Runtime自定义控制器POP手势动画 http://www.cocoachina.com/ios/20150401/11459.html  Objective C运行时(runtime) ...

  7. Python内置函数(30)——super

    英文文档: super([type[, object-or-type]]) Return a proxy object that delegates method calls to a parent ...

  8. Python内置函数(63)——super

    英文文档: super([type[, object-or-type]]) Return a proxy object that delegates method calls to a parent ...

  9. runtime之归档和解档

    IOS开发之NSCoding协议(使用runtime)近期学习IOS的runtime库,然后看到之前写的NSCoding协议有点复杂,如果属性少还好,如果100多个属性,则会显得麻烦.下面使用常规方式 ...

随机推荐

  1. 转:教会你如何编写makefile文件

    最近一直在学习makefile是如何编写的.当我们写的程序文件比较少的时候,敲入gcc /g++,当你在大型工程中,在一个个编译文件的话,你可能就会很郁闷.linux有一个自带的make命令,它让你的 ...

  2. ThinkPHP实现事务回滚示例代码(附加:PDO的事务处理)

    ThinkPHP的事务回滚示例如下: $m=D('YourModel');//或者是M(); $m2=D('YouModel2'); $m->startTrans();//在第一个模型里启用就可 ...

  3. 【JS】jQuery设置定时器,访问服务器(PHP示例)配合微信、支付宝原生支付,跳转web网页

    废话不多说,直接上代码: 把下面的js放在html的</body>即可 <!-- 设置页面定时器,3秒查询一次数据库该订单状态,ok就跳走 --xzz1115> <scr ...

  4. Python 开发者的 6 个必备库,你都了解吗?

    无论你是正在使用 Python 进行快速开发,还是在为 Python 桌面应用制作原生 UI ,或者是在优化现有的 Python 代码,以下这些 Python 项目都是应该使用的. Python那些事 ...

  5. java之final

    我们先看一道面试题: 请问 final 的含义是什么?可以用在哪里?其初始化的方式有哪些? 首先我们回答一下这道题,然后再探究其所以然.  1.final 表示“最终的”.“不可改变的”,意指其修饰类 ...

  6. DirectShow多媒体流捕获播放组件

    英文原文地址:https://msdn.microsoft.com/en-us/library/windows/desktop/dd390351(v=vs.85).aspx Microsoft®Dir ...

  7. VS Code - Debugger for Chrome调试js

    最近在自学一些s的内容,用到了vscode来编写代码,一直没有使用过vs调试js的代码,看到左侧有推荐一个插件Debugger for Chrome使用来调试js代码的,对于vs这种开源,需要安装各种 ...

  8. s:if 标签用法总结和举例

    http://www.360doc.com/content/11/1108/18/6161903_162838014.shtml

  9. blender, 同时选择多个顶点

    法1:按MRB(鼠标右键)选中第一个顶点,再按shift+MRB依次选择其余顶点. 法2:按B,光标变为横纵两条虚线,此时可按MLB(鼠标左键)框选顶点.按MRB结束框选. 法3:按C,光标变为虚线圆 ...

  10. Genymotion INSTALL_FAILED_CPU_ABI_INCOMPATIBLE

    出现这个错误的原因是Genymotion默认的处理器是x86的,不是arm的.所以安装的时候会出错,仅仅要把x86转换成arm就能够了. 转换方法: 1.下载转换的zip文件:X86 to ARM 2 ...