一、Objective-C 中的基本类型

首先看下 Objective-C 的对象模型,每个 Objective-C 对象都是一个指向 Class 的指针。Class 的结构如下:

struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif } OBJC2_UNAVAILABLE;

这个结构已经有很多的说明了,下面简单的再描述下

1. 变量列表

变量 Ivar 也是一个结构体,每个 Class 中用变长结构体的方式存储了 Class 的变量列表。 IVar 的定义如下,包含 名称、类型、偏移、占用空间。

typedef struct objc_ivar *Ivar;

struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

这个变长结构体定义如下:

struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

2. 方法列表

每个方法 Method 的定义如下,包含 SEL 指向对外的命名,char * 型 的方法类型, IMP 方法指针,指向具体的函数实现。

typedef struct objc_method *Method;

struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

同样一个变长结构体来存储方法列表。Class 中的这个列表是个2级指针,所以可以向 Class 中动态的添加方法。

struct objc_method_list {
struct objc_method_list * _Nullable 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;

3. 缓存

同样一个变长结构体存储之前找到的 Method。

1)、mask:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1;

2)、occupied:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目。

他是通过 要查找的 Method 的 SEL 地址和 mask 做一系列运算来确定 Method 的存储与查找位置。更详细的说明可以看参考4。其中提到的几点也在说下:

子类的 cache 会存储在父类中找到的方法;cache 的大小会动态增加,但是增加之前一定会先清空自己(变长结构体的特性)。

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

#define CACHE_BUCKET_NAME(B)  ((B)->method_name)
#define CACHE_BUCKET_IMP(B) ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};

4. 协议

typedef struct objc_category *Category;

struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE; struct objc_protocol_list {
struct objc_protocol_list * _Nullable next;
long count;
__unsafe_unretained Protocol * _Nullable list[1];
};

5. isa 和 superClass

看一张经典的图:

isa 表明当前对象所属于的 Class 类型(Class 也是一个对象,Class 的类型叫 MetaClass)。

superClass 表明当前对象从哪个父类派生出来的,根类型(比如 NSObject、NSProxy)的 superClass 是 nil。

向对象发送消息时,会去方法列表里面查询,找不到会去父类的方法列表,再找不到会进入动态添加、消息转发、消息包装的过程。向 Class 发送消息时,会去 MetaClass 走同样的过程。

二、self 和 super

  1. self 是类的隐藏的参数,指向当前调用方法的类

  2. super 是一个"编译器指示符", 是一个标记,告诉编译器起始于当前类的父类方法列表中搜索方法的实现。

看一个例子

@A
- (void)show{
} - (void)log {
NSLog(@"i am a");
} - (void)print {
NSLog(@"i am %@",[self class]);
} @end @B: A - (void)show
{
[self/super log];
[self/super print];
} - (void)log {
NSLog(@"i am b");
} - (void)print {
NSLog(@"i am %@",[self class]);
} @end @ C: B
- (void)log {
NSLog(@"i am c");
} @end

在 B 的show 方法中分别改成 self 和 super,如下调用会输出什么?

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

结果是 self 的时候 输出

i am c
i am C

super 的时候输出

i am a
i am C

用 self 调用方法,会编译成 objc_msgSend 方法,其定义如下:

void objc_msgSend(void /* id self, SEL op, ... */ )

第一个参数是消息接收者,也就是对象本身,第二个参数是调用的具体类方法的 selector。这里有个隐藏参数 _cmd,代表当前类方法的selector。

用super 调用方法,会编译成 objc_msgSendSuper 方法,其定义如下:

void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

其中 objc_super  的定义如下:

/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; /// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};

三、消息转发

当向一个类的实例发送方法时,会去 Class 结构的方法缓存列表 objc_cache  和 方法列表 objc_method_list  中查找有没有这个方法,如果没有的话,则会进入消息转发阶段。

消息转发主要分为两大阶段:

  1. 动态方法解析:看对象所属类是否能动态添加方法

  2. 转发阶段:既然第一步已经不会新增方法来响应,那系统就会请接受者看看有没有其他对象响应这个消息;如果没有,就把消息封装到 NSInvocation中,再做一次尝试。

参考:

1.http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html

2.http://time-track.cn/variable-length-structure.html

3.https://tech.meituan.com/DiveIntoMethodCache.html

4.http://blog.csdn.net/datacloud/article/details/7275170

5.http://blog.csdn.net/wzzvictory/article/details/8487111

Objective-C 方法交换实践(一) - 基础知识的更多相关文章

  1. Python入门方法推荐,哪些基础知识必学?

    很多想入门的小伙伴还不知道Python应该怎么学,哪些知识必学,今天我们就来盘点一下. 01.入门方法推荐 总体来讲,找一本靠谱的书,由浅入深,边看边练. 网上的学习教程有很多,多到不知道如何选择.所 ...

  2. 《Python编程:从入门到实践》基础知识部分学习笔记整理

    简介 此笔记为<Python编程:从入门到实践>中前 11 章的基础知识部分的学习笔记,不包含后面的项目部分. 书籍评价 从系统学习 Python 的角度,不推荐此书,个人更推荐使用< ...

  3. Objective-C 方法交换实践(二) - 方法指针交换

    一. 基本函数 根据 sel 得到 class 的实例方法 Method class_getInstanceMethod(Class cls, SEL name) 根据 sel 得到 class 的函 ...

  4. Objective-C 方法交换实践(三) - Aspects 源码解析

    一.类与变量 AspectOptions typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// 原方 ...

  5. 学习nginx从入门到实践(四) 基础知识之nginx基本配置语法

    nginx基本配置语法 1.http相关 展示每次请求的请求头: curl -v http://www.baidu.com 2.nginx日志类型 error.log. access.log log_ ...

  6. IOS开发基础知识碎片-导航

    1:IOS开发基础知识--碎片1 a:NSString与NSInteger的互换 b:Objective-c中集合里面不能存放基础类型,比如int string float等,只能把它们转化成对象才可 ...

  7. 20151024_004_C#基础知识(C#中的访问修饰符,继承,new关键字,里氏转换,is 和 as,多态,序列化与反序列化)

    1:C#中的访问修饰符 public: 公共成员,完全公开,没有访问限制. private: 私有的,只能在当前类的内部访问. protected: 受保护的,只能在当前类的内部以及该类的子类中访问. ...

  8. kubebuilder实战之三:基础知识速览

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. 0.Python 爬虫之Scrapy入门实践指南(Scrapy基础知识)

    目录 0.0.Scrapy基础 0.1.Scrapy 框架图 0.2.Scrapy主要包括了以下组件: 0.3.Scrapy简单示例如下: 0.4.Scrapy运行流程如下: 0.5.还有什么? 0. ...

随机推荐

  1. UVa 1640 - The Counting Problem(数论)

    链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  2. BootStrap启动类

    来源 本文整理自 <Tomcat内核设计剖析>.<Tomcat结构解析> 加上自己的理解.源码来自 Tomcat8.5 版本 // org.apache.catalina.st ...

  3. 洛谷P2336 [SCOI2012]喵星球上的点名(后缀数组+莫队)

    我学AC自动机的时候就看到了这题,想用AC自动机结果被学长码风劝退-- 学后缀数组时又看到了这题--那就写写后缀数组做法吧 结果码风貌似比当年劝退我的学长还毒瘤啊 对所有的模式串+询问串,不同串之间用 ...

  4. vue每次请求加头部(shiro+vue)

    前后台分离,全局请求加头部 设置全局请求为ajax请求 _axios.interceptors.request.use( function(config) { var accessToken = lo ...

  5. PAT——1046. 划拳

    划拳是古老中国酒文化的一个有趣的组成部分.酒桌上两人划拳的方法为:每人口中喊出一个数字,同时用手比划出一个数字.如果谁比划出的数字正好等于两人喊出的数字之和,谁就赢了,输家罚一杯酒.两人同赢或两人同输 ...

  6. centos 腾讯云 今天买了 18个月

    1.安装开发编译工具包 yum groupinstall "Development Tools" 2.查看Linux 下用户 对系统资源的占用情况,root表示用户 top -u ...

  7. HTML5与CSS3网页设计

    <!--一个网页的基本结构写法--> <!doctype html> <html> <head>//头部 <title>标题</tit ...

  8. [java之设计模式]策略模式

    策略模式(strategy pattern) 定义>> 将一系列的算法封装到一些列的类里面,并且可以相互替换 作用>> 将算法的变化独立于客户端,将算法的指责和算法的行为分开, ...

  9. 轻量ORM-SqlRepoEx (十七)SqlRepoEx 2.30 版本更新说明

    .Net平台下兼容.NET Standard 2.0,一个实现以Lambda表达式转转换标准SQL语句,支持MySQL.SQL Server数据库方言,使用强类型操作数据的轻量级ORM工具,在减少魔法 ...

  10. 01迷宫题解(bfs,联通块)

    题目https://www.luogu.org/problemnew/show/P1141 这个题解主要针对我个人出现的一些问题和注意的地方. 解题思路 首先说一下联通块 联通块这个比较抽象,举个例子 ...