热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

iOS开发:『Runtime』详解(一)基础知识

我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:而

我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤: 编译链接运行 。不同的编译语言,在这三个步骤中所进行的操作又有些不同。

C 语言 作为一门静态类语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现。

Objective-C 语言 是一门动态语言。在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。

Objective-C 语言 把一些决定性的工作从编译阶段、链接阶段推迟到 运行时阶段 的机制,使得 Objective-C 变得更加灵活。我们甚至可以在程序运行的时候,动态的去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性。

而实现 Objective-C 语言 运行时机制 的一切基础就是 Runtime

Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。

2. 消息机制的基本原理

Objective-C 语言 中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。

我们来看看方法调用 [receiver selector]; 在『编译阶段』和『运行阶段』分别做了什么?

  1. 编译阶段: [receiver selector]; 方法被编译器转换为:
    objc_msgSend(receiver,selector)
    objc_msgSend(recevier,selector,org1,org2,…)
    
  2. 运行时阶段:消息接受者 recever 寻找对应的 selector
    1. 通过 recevierisa 指针 找到 recevierClass(类)
    2. Class(类)method list(方法列表) 中找对应的 selector
    3. 如果在 Class(类) 中没有找到这个 selector ,就继续在它的 superClass(父类) 中寻找;
    4. 一旦找到对应的 selector ,直接执行 recever 对应 selector 方法实现的 IMP(方法实现)
    5. 若找不到对应的 selector ,消息被转发或者临时向 recever 添加这个 selector 对应的实现方法,否则就会发生崩溃。

在上述过程中涉及了好几个新的概念: objc_msgSendisa 指针Class(类)IMP(方法实现) 等,下面我们来具体讲解一下各个概念的含义。

3. Runtime 中的概念解析

3.1 objc_msgSend

所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。 objc_msgSend(receiver,selector);[receiver selector]; 对应的 C 函数。

3.2 Class(类)

objc/runtime.h 中, Class(类) 被定义为指向 objc_class 结构体 的指针, objc_class 结构体 的数据结构如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // objc_class 结构体的实例指针

#if !__OBJC2__
    Class _Nullable super_class;                                 // 指向父类的指针
    const char * _Nonnull name;                                  // 类的名字
    long version;                                                // 类的版本信息,默认为 0
    long info;                                                   // 类的信息,供运行期使用的一些位标识
    long instance_size;                                          // 该类的实例变量大小;
    struct objc_ivar_list * _Nullable ivars;                     // 该类的实例变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
    struct objc_cache * _Nonnull cache;                          // 方法缓存
    struct objc_protocol_list * _Nullable protocols;             // 遵守的协议列表
#endif

};

从中可以看出, objc_class 结构体 定义了很多变量:自身的所有实例变量(ivars)、所有方法定义(methodLists)、遵守的协议列表(protocols)等。 objc_class 结构体 存放的数据称为 元数据(metadata)

objc_class 结构体 的第一个成员变量是 isa 指针isa 指针 保存的是所属类的结构体的实例的指针,这里保存的就是 objc_class 结构体 的实例指针,而实例换个名字就是 对象 。换句话说, Class(类) 的本质其实就是一个对象,我们称之为 类对象

3.3 Object(对象)

接下来,我们再来看看 objc/objc.h 中关于 Object(对象) 的定义。 Object(对象) 被定义为 objc_object 结构体 ,其数据结构如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa;       // objc_object 结构体的实例指针
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

这里的 id 被定义为一个指向 objc_object 结构体 的指针。从中可以看出 objc_object 结构体 只包含一个 Class 类型的 isa 指针

换句话说,一个 Object(对象) 唯一保存的就是它所属 Class(类) 的地址。当我们对一个对象,进行方法调用时,比如 [receiver selector]; ,它会通过 objc_object 结构体isa 指针 去找对应的 object_class 结构体 ,然后在 object_class 结构体methodLists(方法列表) 中找到我们调用的方法,然后执行。

3.4 Meta Class(元类)

从上边我们看出, 对象(objc_object 结构体)isa 指针 指向的是对应的 类对象(object_class 结构体) 。那么 类对象(object_class 结构体 )的 isa 指针 又指向什么呢?

object_class 结构体isa 指针 实际上指向的的是 类对象 自身的 Meta Class(元类)

那么什么是 Meta Class(元类)

Meta Class(元类) 就是一个类对象所属的 。一个对象所属的类叫做 类对象 ,而一个类对象所属的类就叫做 元类

Runtime 中把类对象所属类型就叫做 Meta Class(元类) ,用于描述类对象本身所具有的特征,而在元类的 methodLists 中,保存了类的方法链表,即所谓的「类方法」。并且类对象中的 isa 指针 指向的就是元类。每个类对象有且仅有一个与之相关的元类。

2. 消息机制的基本原理 中我们讲解了 对象方法的调用过程 ,我们是通过对象的 isa 指针 找到 对应的 Class(类) ;然后在 Class(类)method list(方法列表) 中找对应的 selector

类方法的调用过程 和对象方法调用差不多,流程如下:

  1. 通过类对象 isa 指针 找到所属的 Meta Class(元类)
  2. Meta Class(元类)method list(方法列表) 中找到对应的 selector ;
  3. 执行对应的 selector

下面看一个示例:

NSString *testString = [NSString stringWithFormat:@"%d,%s",3, "test"];

上边的示例中, stringWithFormat: 被发送给了 NSString 类NSString 类 通过 isa 指针 找到 NSString 元类 ,然后在该元类的方法列表中找到对应的 stringWithFormat: 方法,然后执行该方法。

3.5 实例对象、类、元类之间的关系

上面,我们讲解了 实例对象(Object)类(Class)Meta Class(元类) 的基本概念,以及简单的指向关系。下面我们通过一张图来清晰地表示出这种关系。

iOS 开发:『Runtime』详解(一)基础知识

我们先来看 isa 指针

  1. 水平方向上,每一级中的 实例对象isa 指针 指向了对应的 类对象 ,而 类对象isa 指针 指向了对应的 元类 。而所有元类的 isa 指针 最终指向了 NSObject 元类 ,因此 NSObject 元类 也被称为 根源类
  2. 垂直方向上, 元类isa 指针父类元类isa 指针 都指向了 根元类 。而 根源类isa 指针 又指向了自己。

我们再来看 父类指针

  1. 类对象父类指针 指向了 父类的类对象父类的类对象 又指向了 根类的类对象根类的类对象 最终指向了 nil。
  2. 元类父类指针 指向了 父类对象的元类父类对象的元类父类指针 指向了 根类对象的元类 ,也就是 根元类 。而 根元类父亲指针 指向了 根类对象 ,最终指向了 nil。

3.6 方法(Method)

object_class 结构体methodLists(方法列表) 中存放的元素就是 方法(Method)

先来看下 objc/runtime.h 中,表示 方法(Method)objc_method 结构体 的数据结构:

/// An opaque type that represents a method in a class definition.
/// 代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name;                    // 方法名
    char * _Nullable method_types;               // 方法类型
    IMP _Nonnull method_imp;                     // 方法实现
};

可以看到, objc_method 结构体 中包含了 方法名(method_name)方法类型(method_types)方法实现(method_imp) 。下面,我们来了解下这三个变量。

  1. SEL method_name; // 方法名
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL 是一个指向 objc_selector 结构体 的指针,但是在 runtime 相关头文件中并没有找到明确的定义。不过,通过测试我们可以得出: SEL 只是一个保存方法名的字符串。

SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel);              // 输出:viewDidLoad
SEL sel1 = @selector(test);
NSLog(@"%s", sel1);             // 输出:test
  1. IMP method_imp; // 方法实现
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 的实质是一个

推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • 本文介绍了GTK+中的GObject对象系统,该系统是基于GLib和C语言完成的面向对象的框架,提供了灵活、可扩展且易于映射到其他语言的特性。其中最重要的是GType,它是GLib运行时类型认证和管理系统的基础,通过注册和管理基本数据类型、用户定义对象和界面类型来实现对象的继承。文章详细解释了GObject系统中对象的三个部分:唯一的ID标识、类结构和实例结构。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • ECMA262规定typeof操作符的返回值和instanceof的使用方法
    本文介绍了ECMA262规定的typeof操作符对不同类型的变量的返回值,以及instanceof操作符的使用方法。同时还提到了在不同浏览器中对正则表达式应用typeof操作符的返回值的差异。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
author-avatar
手机用户2602901285
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有