区块链技术博客
www.b2bchain.cn

函数派发机制 & iOS消息传递机制求职学习资料

本文介绍了函数派发机制 & iOS消息传递机制求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

函数派发机制

先来了解下 编译型语言非编译型语言 的区别吧:

编译型和非编译型参考自这篇知乎文章

  • 编译型语言:需要将高级语言(比如C/OC)需要通过编译器转换成平台(比如Windows/iOS/Android)能够理解的机器代码(汇编或者说二进制,因为不同系统架构的命令集不一样);比如对 C 语言或者其他编译型语言来说,编译生成了目标文件,而这个目标文件是针对特定的 CPU 体系的,为 ARM 生成的目标文件,不能被用于 MIPS 的 CPU。这段代码在编译过程中就已经被翻译成了目标 CPU 指令,所以,如果这个程序需要在另外一种 CPU 上面运行,这个代码就必须重新编译

  • 非编译型语言:对于各种非编译型语言(例如python/java)来说,同样也可能存在某种编译过程,但他们编译生成的通常是一种『平台无关』的中间代码,这种代码一般不针对特定的 CPU 平台,他们是在运行过程中才被翻译成目标 CPU 指令的,因而,在 ARM CPU 上能执行,换到 MIPS 也能执行,换到 X86 也能执行,不需要重新对源代码进行编译

函数派发主要分为:直接派发(Direct Patch)、函数表派发(Table Patch)、消息机制派发(Message Patch),而函数派发的目的就是让CPU得到函数实现的真正地址

直接派发

直接派发又称为静态调用,调用速度最快;本质上是在编译过程中就把函数调用替换成函数实现的地址

举个🌰:

static void just_print_hello(NSString *name) {     NSLog(@"hello %@",name); }  just_print_hello(@"bibiking");

定义一个C函数,然后直接调用就行了

实际上,无论哪种派发方式,最终都需要调用函数实现的真正指针,所以直接派发的速度是最快,但缺点是缺少动态性,无法override。C/C++就是采用的直接派发。

函数表派发

直接上例子吧:

class ParentClass {     func method1() {}     func method2() {} } class ChildClass: ParentClass {     override func method2() {}     func method3() {} }

在这个情况下, 编译器会创建两个函数表, 一个是 ParentClass 的, 另一个是 ChildClass的:
函数派发机制 & iOS消息传递机制

每个类都会维护一个函数表,存放着函数名和对应的实现指针,如果override了则会存放新的函数实现地址,子类新定义的方法则会放到表最后

let obj = ChildClass() obj.method2()

当一个函数被调用时, 会经历下面的几个过程:

  1. 读取对象 0xB00 的函数表.
  2. 读取函数指针的索引. 在这里, method2 的索引是1(偏移量), 也就是 0xB00 + 1.
  3. 跳到 0x222 (函数指针指向 0x222)

特殊的,C++可以使用Virtural关键字将直接派发转为函数表派发的方式

比如:

class Animal {     public:         void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };  void func(Animal *xyz) { xyz->eat(); }  Animal *animal = new Animal; Cat *cat = new Cat;  func(animal); // Outputs: "I'm eating generic food." func(cat);    // Outputs: "I'm eating generic food." 

会发现输出都是Animal的方式输出,这是因为在编译的时候编译器就将func函数的xyz->eat()直接转成了类Animaleat函数实现地址,所以无论对象是谁,总会调用Animaleat实现。

下面是采用Virtual关键字:

class Animal {     public:         virtual void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };

此时,当编译器编译函数func里的xyz->eat()时,发现Animaleat方法标识成了Virtual,所以会直接转成实现指针地址,而是建了个上面图示的虚函数表,当真正运行的时候在按照虚函数表来找实现

消息机制派发

OC用的主要就是消息派发机制,先用OC调用方法:

Person *p = Person.new; [p sayHello];

其实本质上调用了:

((void (*)(id,SEL))objc_msgSend)(p,@user1(sayHello));

这个C方法

注意:需要#import <objc/message.h>
message.h里声明的objc_msgSend是这样的objc_msgSend(void /* id self, SEL op, ... */ ),是没有参数的,所以用的时候要强制转换一下类型

然后objc_msgSend才会开始消息传递之旅

runtime库的源码自取,数字越大表示越新

不过objc_msgSend的实现源码是汇编,有点难受了。根据不同的架构给了不同的汇编实现:

函数派发机制 &amp; iOS消息传递机制

这也呼应了文章最开头将的编译型语言非编译型语言,其中编译型便是将高级语言编译成特定架构能够理解的汇编语言

所以就不看源码了,也看不懂,直接看博客好了

函数派发机制 &amp; iOS消息传递机制

下面结合底层结构来解释下具体的步骤:

// NSObject.h  NSObject <NSObject> {     Class isa  OBJC_ISA_AVAILABILITY; }  // objc-private.h typedef struct objc_class  *Class; typedef struct objc_object *id;  struct objc_object { private:     isa_t isa;  public:     ... }  union isa_t {     Class cls;     ... };  //objc-runtime-old.h struct objc_class : objc_object {     Class superclass;     const char *name;     uint32_t version;     uint32_t info;     uint32_t instance_size;     struct old_ivar_list *ivars;     struct old_method_list **methodLists;     Cache cache;     struct old_protocol_list *protocols;     ... }  // objc-runtime-new.h struct objc_class : objc_object {     // Class ISA;     Class superclass;     cache_t cache;               class_data_bits_t bits;     ... }  // runtime.h typedef struct objc_cache *Cache struct objc_cache {     unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;     unsigned int occupied                                    OBJC2_UNAVAILABLE;     Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE; };

Class有新旧两个实现,我们主要看objc-runtime-old比较直观,新的增加Swift一些支持和其他特性

通过上面乱七八糟的结构体的isasuperclass,我们就得到了面试的经典图片:
<img src=”https://images.xiaozhuanlan.com/photo/2020/48a542e29de70633d36bd3b95bdf62b7.jpg” alt=”图片替换文本” width=”400″ height=”400″ />


OC 和 Swift 混编

同一工程内的OC和Swift混编参考这篇博客,同一工程是指OC和Swift混编不是跨Pod的,是同一工程内同时存在.h/.m.swift并且能够相互调用


命名空间

主要参考这篇文章

OC有一个我们习以为常的特点,就是所有类都要加前缀,不然很容易和其他人建的类冲突,所以我们写了类似于XXViewController等类名

但Swift作为一门现代语言,自然有命名空间这种东西,比如系统提供的类就已经不加前缀了UIView -> View等,因为Module已经作为一层命名空间了,也就是UIKit,更多信息可以参考上面的博客

函数派发机制

先来了解下 编译型语言非编译型语言 的区别吧:

编译型和非编译型参考自这篇知乎文章

  • 编译型语言:需要将高级语言(比如C/OC)需要通过编译器转换成平台(比如Windows/iOS/Android)能够理解的机器代码(汇编或者说二进制,因为不同系统架构的命令集不一样);比如对 C 语言或者其他编译型语言来说,编译生成了目标文件,而这个目标文件是针对特定的 CPU 体系的,为 ARM 生成的目标文件,不能被用于 MIPS 的 CPU。这段代码在编译过程中就已经被翻译成了目标 CPU 指令,所以,如果这个程序需要在另外一种 CPU 上面运行,这个代码就必须重新编译

  • 非编译型语言:对于各种非编译型语言(例如python/java)来说,同样也可能存在某种编译过程,但他们编译生成的通常是一种『平台无关』的中间代码,这种代码一般不针对特定的 CPU 平台,他们是在运行过程中才被翻译成目标 CPU 指令的,因而,在 ARM CPU 上能执行,换到 MIPS 也能执行,换到 X86 也能执行,不需要重新对源代码进行编译

函数派发主要分为:直接派发(Direct Patch)、函数表派发(Table Patch)、消息机制派发(Message Patch),而函数派发的目的就是让CPU得到函数实现的真正地址

直接派发

直接派发又称为静态调用,调用速度最快;本质上是在编译过程中就把函数调用替换成函数实现的地址

举个🌰:

static void just_print_hello(NSString *name) {     NSLog(@"hello %@",name); }  just_print_hello(@"bibiking");

定义一个C函数,然后直接调用就行了

实际上,无论哪种派发方式,最终都需要调用函数实现的真正指针,所以直接派发的速度是最快,但缺点是缺少动态性,无法override。C/C++就是采用的直接派发。

函数表派发

直接上例子吧:

class ParentClass {     func method1() {}     func method2() {} } class ChildClass: ParentClass {     override func method2() {}     func method3() {} }

在这个情况下, 编译器会创建两个函数表, 一个是 ParentClass 的, 另一个是 ChildClass的:
函数派发机制 &amp; iOS消息传递机制

每个类都会维护一个函数表,存放着函数名和对应的实现指针,如果override了则会存放新的函数实现地址,子类新定义的方法则会放到表最后

let obj = ChildClass() obj.method2()

当一个函数被调用时, 会经历下面的几个过程:

  1. 读取对象 0xB00 的函数表.
  2. 读取函数指针的索引. 在这里, method2 的索引是1(偏移量), 也就是 0xB00 + 1.
  3. 跳到 0x222 (函数指针指向 0x222)

特殊的,C++可以使用Virtural关键字将直接派发转为函数表派发的方式

比如:

class Animal {     public:         void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };  void func(Animal *xyz) { xyz->eat(); }  Animal *animal = new Animal; Cat *cat = new Cat;  func(animal); // Outputs: "I'm eating generic food." func(cat);    // Outputs: "I'm eating generic food." 

会发现输出都是Animal的方式输出,这是因为在编译的时候编译器就将func函数的xyz->eat()直接转成了类Animaleat函数实现地址,所以无论对象是谁,总会调用Animaleat实现。

下面是采用Virtual关键字:

class Animal {     public:         virtual void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };

此时,当编译器编译函数func里的xyz->eat()时,发现Animaleat方法标识成了Virtual,所以会直接转成实现指针地址,而是建了个上面图示的虚函数表,当真正运行的时候在按照虚函数表来找实现

消息机制派发

OC用的主要就是消息派发机制,先用OC调用方法:

Person *p = Person.new; [p sayHello];

其实本质上调用了:

((void (*)(id,SEL))objc_msgSend)(p,@user1(sayHello));

这个C方法

注意:需要#import <objc/message.h>
message.h里声明的objc_msgSend是这样的objc_msgSend(void /* id self, SEL op, ... */ ),是没有参数的,所以用的时候要强制转换一下类型

然后objc_msgSend才会开始消息传递之旅

runtime库的源码自取,数字越大表示越新

不过objc_msgSend的实现源码是汇编,有点难受了。根据不同的架构给了不同的汇编实现:

函数派发机制 &amp; iOS消息传递机制

这也呼应了文章最开头将的编译型语言非编译型语言,其中编译型便是将高级语言编译成特定架构能够理解的汇编语言

所以就不看源码了,也看不懂,直接看博客好了

函数派发机制 &amp; iOS消息传递机制

下面结合底层结构来解释下具体的步骤:

// NSObject.h  NSObject <NSObject> {     Class isa  OBJC_ISA_AVAILABILITY; }  // objc-private.h typedef struct objc_class  *Class; typedef struct objc_object *id;  struct objc_object { private:     isa_t isa;  public:     ... }  union isa_t {     Class cls;     ... };  //objc-runtime-old.h struct objc_class : objc_object {     Class superclass;     const char *name;     uint32_t version;     uint32_t info;     uint32_t instance_size;     struct old_ivar_list *ivars;     struct old_method_list **methodLists;     Cache cache;     struct old_protocol_list *protocols;     ... }  // objc-runtime-new.h struct objc_class : objc_object {     // Class ISA;     Class superclass;     cache_t cache;               class_data_bits_t bits;     ... }  // runtime.h typedef struct objc_cache *Cache struct objc_cache {     unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;     unsigned int occupied                                    OBJC2_UNAVAILABLE;     Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE; };

Class有新旧两个实现,我们主要看objc-runtime-old比较直观,新的增加Swift一些支持和其他特性

通过上面乱七八糟的结构体的isasuperclass,我们就得到了面试的经典图片:
<img src=”https://images.xiaozhuanlan.com/photo/2020/48a542e29de70633d36bd3b95bdf62b7.jpg” alt=”图片替换文本” width=”400″ height=”400″ />


OC 和 Swift 混编

同一工程内的OC和Swift混编参考这篇博客,同一工程是指OC和Swift混编不是跨Pod的,是同一工程内同时存在.h/.m.swift并且能够相互调用


命名空间

主要参考这篇文章

OC有一个我们习以为常的特点,就是所有类都要加前缀,不然很容易和其他人建的类冲突,所以我们写了类似于XXViewController等类名

但Swift作为一门现代语言,自然有命名空间这种东西,比如系统提供的类就已经不加前缀了UIView -> View等,因为Module已经作为一层命名空间了,也就是UIKit,更多信息可以参考上面的博客

函数派发机制

先来了解下 编译型语言非编译型语言 的区别吧:

编译型和非编译型参考自这篇知乎文章

  • 编译型语言:需要将高级语言(比如C/OC)需要通过编译器转换成平台(比如Windows/iOS/Android)能够理解的机器代码(汇编或者说二进制,因为不同系统架构的命令集不一样);比如对 C 语言或者其他编译型语言来说,编译生成了目标文件,而这个目标文件是针对特定的 CPU 体系的,为 ARM 生成的目标文件,不能被用于 MIPS 的 CPU。这段代码在编译过程中就已经被翻译成了目标 CPU 指令,所以,如果这个程序需要在另外一种 CPU 上面运行,这个代码就必须重新编译

  • 非编译型语言:对于各种非编译型语言(例如python/java)来说,同样也可能存在某种编译过程,但他们编译生成的通常是一种『平台无关』的中间代码,这种代码一般不针对特定的 CPU 平台,他们是在运行过程中才被翻译成目标 CPU 指令的,因而,在 ARM CPU 上能执行,换到 MIPS 也能执行,换到 X86 也能执行,不需要重新对源代码进行编译

函数派发主要分为:直接派发(Direct Patch)、函数表派发(Table Patch)、消息机制派发(Message Patch),而函数派发的目的就是让CPU得到函数实现的真正地址

直接派发

直接派发又称为静态调用,调用速度最快;本质上是在编译过程中就把函数调用替换成函数实现的地址

举个🌰:

static void just_print_hello(NSString *name) {     NSLog(@"hello %@",name); }  just_print_hello(@"bibiking");

定义一个C函数,然后直接调用就行了

实际上,无论哪种派发方式,最终都需要调用函数实现的真正指针,所以直接派发的速度是最快,但缺点是缺少动态性,无法override。C/C++就是采用的直接派发。

函数表派发

直接上例子吧:

class ParentClass {     func method1() {}     func method2() {} } class ChildClass: ParentClass {     override func method2() {}     func method3() {} }

在这个情况下, 编译器会创建两个函数表, 一个是 ParentClass 的, 另一个是 ChildClass的:
函数派发机制 &amp; iOS消息传递机制

每个类都会维护一个函数表,存放着函数名和对应的实现指针,如果override了则会存放新的函数实现地址,子类新定义的方法则会放到表最后

let obj = ChildClass() obj.method2()

当一个函数被调用时, 会经历下面的几个过程:

  1. 读取对象 0xB00 的函数表.
  2. 读取函数指针的索引. 在这里, method2 的索引是1(偏移量), 也就是 0xB00 + 1.
  3. 跳到 0x222 (函数指针指向 0x222)

特殊的,C++可以使用Virtural关键字将直接派发转为函数表派发的方式

比如:

class Animal {     public:         void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };  void func(Animal *xyz) { xyz->eat(); }  Animal *animal = new Animal; Cat *cat = new Cat;  func(animal); // Outputs: "I'm eating generic food." func(cat);    // Outputs: "I'm eating generic food." 

会发现输出都是Animal的方式输出,这是因为在编译的时候编译器就将func函数的xyz->eat()直接转成了类Animaleat函数实现地址,所以无论对象是谁,总会调用Animaleat实现。

下面是采用Virtual关键字:

class Animal {     public:         virtual void eat() { std::cout << "I'm eating generic food."; } };  class Cat : public Animal {     public:         void eat() { std::cout << "I'm eating a rat."; } };

此时,当编译器编译函数func里的xyz->eat()时,发现Animaleat方法标识成了Virtual,所以会直接转成实现指针地址,而是建了个上面图示的虚函数表,当真正运行的时候在按照虚函数表来找实现

消息机制派发

OC用的主要就是消息派发机制,先用OC调用方法:

Person *p = Person.new; [p sayHello];

其实本质上调用了:

((void (*)(id,SEL))objc_msgSend)(p,@user1(sayHello));

这个C方法

注意:需要#import <objc/message.h>
message.h里声明的objc_msgSend是这样的objc_msgSend(void /* id self, SEL op, ... */ ),是没有参数的,所以用的时候要强制转换一下类型

然后objc_msgSend才会开始消息传递之旅

runtime库的源码自取,数字越大表示越新

不过objc_msgSend的实现源码是汇编,有点难受了。根据不同的架构给了不同的汇编实现:

函数派发机制 &amp; iOS消息传递机制

这也呼应了文章最开头将的编译型语言非编译型语言,其中编译型便是将高级语言编译成特定架构能够理解的汇编语言

所以就不看源码了,也看不懂,直接看博客好了

函数派发机制 &amp; iOS消息传递机制

下面结合底层结构来解释下具体的步骤:

// NSObject.h  NSObject <NSObject> {     Class isa  OBJC_ISA_AVAILABILITY; }  // objc-private.h typedef struct objc_class  *Class; typedef struct objc_object *id;  struct objc_object { private:     isa_t isa;  public:     ... }  union isa_t {     Class cls;     ... };  //objc-runtime-old.h struct objc_class : objc_object {     Class superclass;     const char *name;     uint32_t version;     uint32_t info;     uint32_t instance_size;     struct old_ivar_list *ivars;     struct old_method_list **methodLists;     Cache cache;     struct old_protocol_list *protocols;     ... }  // objc-runtime-new.h struct objc_class : objc_object {     // Class ISA;     Class superclass;     cache_t cache;               class_data_bits_t bits;     ... }  // runtime.h typedef struct objc_cache *Cache struct objc_cache {     unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;     unsigned int occupied                                    OBJC2_UNAVAILABLE;     Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE; };

Class有新旧两个实现,我们主要看objc-runtime-old比较直观,新的增加Swift一些支持和其他特性

通过上面乱七八糟的结构体的isasuperclass,我们就得到了面试的经典图片:
<img src=”https://images.xiaozhuanlan.com/photo/2020/48a542e29de70633d36bd3b95bdf62b7.jpg” alt=”图片替换文本” width=”400″ height=”400″ />


OC 和 Swift 混编

同一工程内的OC和Swift混编参考这篇博客,同一工程是指OC和Swift混编不是跨Pod的,是同一工程内同时存在.h/.m.swift并且能够相互调用


命名空间

主要参考这篇文章

OC有一个我们习以为常的特点,就是所有类都要加前缀,不然很容易和其他人建的类冲突,所以我们写了类似于XXViewController等类名

但Swift作为一门现代语言,自然有命名空间这种东西,比如系统提供的类就已经不加前缀了UIView -> View等,因为Module已经作为一层命名空间了,也就是UIKit,更多信息可以参考上面的博客

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 函数派发机制 & iOS消息传递机制求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们