IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 问题汇总-Runtime -> 正文阅读

[移动开发]问题汇总-Runtime

题目链接
掘金-JunesYin

一 结构模型

1 runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等)

信息来源:阿里云开发者社区

1.1 对象

OC中的对象指向的是一个objc_object指针类型,typedef struct objc_object *id;
从它的结构体中可以看出,它包括一个isa指针,指向的是这个对象的类对象,一个对象实例就是通过这个isa找到它自己的Class,而这个Class中存储的就是这个实例的方法列表、属性列表、成员变量列表等相关信息的。
请添加图片描述

1.2 类

类:在OC中的类是用Class来表示的,实际上它指向的是一个objc_class的指针类型,typedef struct objc_class *Class;对应的结构体如下:

从结构体中定义的变量可知,OC的Class类型包括如下数据(即:元数据metadata):super_class(父类类对象);name(类对象的名称);version、info(版本和相关信息);instance_size(实例内存大小);ivars(实例变量列表);methodLists(方法列表);cache(缓存);protocols(实现的协议列表);

当然也包括一个isa指针,这说明Class也是一个对象类型,所以我们称之为类对象,这里的isa指向的是元类对象(metaclass),元类中保存了创建类对象(Class)的类方法的全部信息。

在这里插入图片描述
以下图中可以清楚的了解到OC对象、类、元类之间的关系。

从图中可知,最终的基类(NSObject)的元类对象isa指向的是自己本身,从而形成一个闭环。
元类(Meta Class):是一个类对象的类,即:Class的类,这里保存了类方法等相关信息。
类对象中存储的方法、属性、成员变量等信息的结构体
objc_ivar_list:存储了类的成员变量,可以通过object_getIvar或class_copyIvarList获取;
另外这两个方法是用来获取类的属性列表的class_getProperty和class_copyPropertyList,属性和成员变量是有区别的。

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;
} 

objc_method_list:存储了类的方法列表,可以通过class_copyMethodList获取。
结构体如下:

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

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;
} 

objc_protocol_list:储存了类的协议列表,可以通过class_copyProtocolList获取。

struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

2 为什么要设计metaclass

信息来源:简书-robin2005

2.1 metaclass中的消息转发机制

只用chcheLookup找方法缓存,命中则直接调用,未命中,则Object_msgSend_uncached
若是还未命中,则

2.2 元类的存在

走到这里一套方法发送的流程就都走完了,那这跟元类的存在有啥关系?我们都知道类方法是存储在元类中的,那么可不可以把元类干掉,在类中把实例方法和类方法 存在两个不同的副本中?

答:行是肯定可行的,但是在lookUpImpOrForward执行的时候就得标注上预期的cls到底是实例对象还是类对象,这也就意味着在查找方法的缓存时同样也需要判断cls到底是个啥。

倘若该该类存在同名的类方法和实例方法是该调用该方法呢?这也就意味着还得给给的该方法带上是类方法还是实例方法的标识,SEL并没有带上当前方法的类型(实例方法还是类方法),参数又多加一个,而我们现在的objc_msgSend()只接收了(id self,SEL _cmd,…)这类型参数,第一个自我就是消息的接收者,第二个就是方法,后续的…就是各式各样的参数。

通过元类就可以准确的解决上述问题,让各类各司其职,实例对象就干存储属性值的事,类对象存储实例方法列表,元类对象存储类方法列表,完美的符合6大设计原则中的单一职责,而且忽略了对对象类型的判断和方法类型的判断可以大大的提升消息发送的效率,并且在不同种类的方法走的都是相同套流程,在之后的维护上也很大节约了成本。

总结

本文从OC的消息机制分析了元类存在的意义,元类的存在简化的简化了实例方法和类方法的调用流程,大大提高了消息发送的效率

3 class_copyIvarList & class_copyPropertyList区别

先看源码

3.1 class_copyPropertyList

class_copyPropertyList返回的仅仅是对象类的属性。

由源码可以看到:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) {
	old_property_list *plist;
	uintptr_t iterator = 0;
	old_property **result = nil;
	```
 	return (objc_property_t *)result;
 }

返回的是一个objc_property_t,而objc_property_t是什么呢?

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
//表示Objective-C声明属性的不透明类型。

3.2 class_copyIvarList

class_copyIvarList遍历了内部所有的信息,成员变量和属性都能访问到

Ivar *class_copyIvarList(Class cls, unsigned int *outCount){
	Ivar *result = nil;
    unsigned int count = 0;
    int i;

    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }

    if (cls->ivars) {
        count = cls->ivars->ivar_count;
    }

    if (count > 0) {
        result = (Ivar *)malloc((count+1) * sizeof(Ivar));

        for (i = 0; i < cls->ivars->ivar_count; i++) {
            result[i] = (Ivar)&cls->ivars->ivar_list[i];
        }
        result[i] = nil;
    }

    if (outCount) *outCount = count;
    return result;
}

3.3 区别和联系

class_copyPropertyList:返回的仅仅是对象类的属性。
class_copyIvarList:成员变量和属性都能访问到。

还有一些在_uDoctor的博客中看到,学习一下~

Q1:通过关联对象方法添加的属性既不在PropertyList中也不再IvarList中,那在哪呢?
A:说明利用runtime添加的属性并不是真正的属性(对象的内存布局不能改变),而是在内存中,另外开辟了内存,把值和key放入AssociationsHashMap中,并关联上了这个对象。取值也是从AssociationsHashMap中取。

Q2:两者为什么都不能访问到父类的成员变量?
A:这个和C++中,class的默认继承权限可能有关系。c++中class的默认继承权限是private,也就是不能访问父类的成员函数,而c++中的struct是可以继承,并且默认继承权限是public

4 class_rw_t 和 class_ro_t 的区别

class_ro_t:一维数组,存储的是类在编译时就确定的一些信息,不可修改的原始核心。
class_rw_t:二维数组提供运行时继承、分类、扩展的能力。

请添加图片描述

5 category如何被加载的,两个category的load方法的加载顺序,两个category的同名方法的加载顺序,+load 方法调用顺序?

简书-水煮杰尼龟

5.1 类的load方法中,能调用分类的方法吗?

是可以的。
首先Runtime的入口函数可以加载分类,加载后调用load方法。由源码可以看到在_objc_init中注册了三个事件,首先是加载Category,接着是调用load方法,所以,是可以调用分类的方法的。
在这里插入图片描述
请添加图片描述

5.2 Category的加载

简书-forping
首先,在编译的时候,分类会被编译成 静态category_t 结构体变量,之后在运行的时候加载,和类对象关联起来。
在编译的时候,实例对象对应的结构体构建完毕(包含了成员变量). 分类在运行时加载,不能再扩充成员变量.

6 category & extension区别,能给NSObject添加Extension吗?

在这里插入图片描述

请添加图片描述

7 消息转发机制,消息转发机制和其他语言的消息机制优劣对比

简书-微妙的语言
阿里云开发者社区
程序员大本营

7.1 消息转发机制是啥?

消息发送机制:使用了运行时,通过selector快速去查找IMP的一个过程
消息转发机制:IMP找不到的时候,通过一些方法做转发处理。实际对象→类对象→元类→根元类

对于C语言,函数的调用在编译的时候会决定调用哪个函数。编译完成之后直接顺序执行,无任何二义性。
OC的函数调用称为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(也就是说,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

7.2 消息转发机制的流程是啥?

消息转发机制:当接收者收到消息后,无法处理该消息时(即:找不到调用的方法SEL),就会启动消息转发机制,流程如下:

第一阶段:咨询接收者,询问它是否可以动态增加这个方法实现

第二阶段:在第一阶段中,接收者无法动态增加这个方法实现,那么系统将询问是否有其他对象可能执行该方法,如果可以,系统将转发给这个对象处理。

第三阶段:在第二阶段中,如果没有其他对象可以处理,那么系统将该消息相关的细节封装成NSInvocation对象,再给接收者最后一次机会,如果这里仍然无法处理,接收者将收到doesNotRecognizeSelector方法调用,此时程序将crash。

// 第一阶段 咨询接收者是否可以动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector //处理的是类方法

// 第二阶段:询问是否有其他对象可以处理
- (id)forwardingTargetForSelector:(SEL)selector

// 第三阶段
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)invocation

7.3 在方法调用的时候,方法查询-> 动态解析-> 消息转发 之前做了什么

1、编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector (makeText));
2、在objc_msgSend函数中,首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method,若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

3、当向someObject发送某消息,但runtime在当前类和父类中都找不到对应方法的实现时,runtime并不会立即报错使程序崩溃,而是依次执行下列步骤:
请添加图片描述
1.动态方法解析:向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法。
2.快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法。若该方法返回值对象非nil或非self,则向该返回对象重新发送消息。
3.标准消息转发:runtime发送methodSignatureForSelector消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。

??这样如果本类中没有的方法,就会去class中尝试查找,如果有,则返回class对象,让其执行,这样就实现了快速消息转发。

7.4 消息转发机制的优劣

8 load、initialize方法的区别什么?在继承关系中他们有什么区别

CSDN-最炫民族风
请添加图片描述

9 IMP、SEL、Method的区别和使用场景

D_猿员

一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。

9.1 IMP

typedef id (*IMP)(id, SEL, ...)

代表函数指针,即函数执行的入口。该函数使用标准的 C调用。
第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;
第二个参数代表方法的选择子;
… 代表可选参数
前面的 id 代表返回值。

9.2 SEL

方法的名称,选择子代表方法在 Runtime期间的标识符。为 SEL类型,虽然 SEL是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C的 Runtime 运行系统。不论两个类是否存在依存关系,只要他们拥有相同的方法名,那么他们的SEL都是相同的。比如,有n个viewcontroller页面,每个页面都有一个viewdidload,每个页面的载入,肯定都是不尽相同的。但是我们可以通过打印,观察发现,这些viewdidload的SEL都是同一个。

SEL sel = @selector(methodName); // 方法名字 
NSLog(@"address = %p",sel);// log输出为 address = 0x1df807e29

因此类方法定义时,尽量不要用相同的名字,就算是变量类型不同也不行。否则会引起重复,例如:

-(void)setWidth:(int)width; 
-(void)setWidth:(double)width;

9.3 Method

typedef struct objc_method *Method

Method对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个objc_method结构体指针,我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。 objc_method的定义为:

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

方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。
method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 Implementation。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-24 10:40:21  更:2021-09-24 10:43:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:57:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码