美文网首页
Category解析

Category解析

作者: summer_maimaiti | 来源:发表于2023-01-11 14:27 被阅读0次

    一、objc 对象的 isa 的指针指向什么?有什么作用?

    指向他的类对象,从而可以找到对象上的方法

    详解:下图很好的描述了对象,类,元类之间的关系:

    [图片上传失败...(image-f3fe67-1661948616530)]

    <figcaption>图中实线是 super_class 指针,虚线是 isa 指针。</figcaption>

    1.Root class (class)其实就是 NSObject,NSObject 是没有超类的,所以 Root class(class)的 superclass

    指向 nil。

    2.每个 Class 都有一个 isa 指针指向唯一的 Meta class

    3.Root class(meta)的 superclass 指向 Root class(class),也就是 NSObject,形成一个回路。

    4.每个 Meta class 的 isa 指针都指向 Root class (meta)。

    二、一个 NSObject 对象占用多少内存空间?

    受限于内存分配的机制,一个 NSObject 对象都会分配 16byte 的内存空间。

    但是实际上在 64 位 下,只使用了 8byte;

    在 32 位下,只使用了 4byte

    一个 NSObject 实例对象成员变量所占的大小,实际上是 8 字节

    [图片上传失败...(image-9f6aec-1661948616530)]

    本质是

    [图片上传失败...(image-708660-1661948616530)]

    获取Obj-C指针所指向的内存的大小,实际上是

    [图片上传失败...(image-a7dcb3-1661948616529)]

    对象在分配内存空间时,会进行内存对齐,所以在iOS中,分配内存空间都是16字节的倍数。

    可以通过以下网址:http://openSource.apple.com/tarballs来查看源代码。

    三、说一下对class_rw_t的理解?

    rw代表可读可写。ObjC类中的属性、方法还有遵循的协议等信息都保存在class_rw_t中:

    [图片上传失败...(image-70997f-1661948616529)]

    四、说一下对class_ro_t的理解?

    存储了当前类在编译期就已经确定的属性、方法以及遵循的协议

    [图片上传失败...(image-939002-1661948616529)]

    五、、说一下对isa指针的理解,对象的isa指针指向哪里?isa指针有哪两种类型?

    isa等价于 is kind of

    • 实例对象isa指向类对象
    • 类对象指isa向元类对象
    • 元类对象的isa指向元类的基类

    isa有两种类型

    • 纯指针,指向内存地址
    • NON_POINTER_ISA,除了内存地址,还存有一些其他信息

    isa源码分析

    在Runtime源码查看sa_t是共用体。简化结构如下:

    [图片上传失败...(image-8790ca-1661948616529)]

    六、说一下untime的方法缓存?存储的形式、数据结构以及查找的过程?

    cache_t增量扩展的哈希表结构。哈希表内部存储的bucket_t。

    bucket_t中存储的是SEL和IMP的键值对。

    • 如果是有序方法列表,采用二分查找
    • 如果是无序方法列表,直接遍历查找

    cache_t结构体

    [图片上传失败...(image-8fa82e-1661948616529)]

    七、使用 runtime Associate 方法关联的对象,需要在主对象 dealloc 的时候释放么?

    无论在 MRC 下还是 ARC 下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在

    被 NSObject -dealloc 调用的 object_dispose()方法中释放。

    [图片上传失败...(image-2b761-1661948616529)]

    [图片上传失败...(image-418550-1661948616529)]

    十、什么时候会报 unrecognized selector 的异常?

    objc 在向一个对象发送消息时,runtime 库会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中

    的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,会

    进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常 unrecognized

    selector sent to XXX 。

    十一、如何给 Category 添加属性?关联对象以什么形式进行存储?

    查看的是 关联对象 的知识点。

    详细的说一下 关联对象。

    关联对象 以哈希表的格式,存储在一个全局的单例中。

    [图片上传失败...(image-8d07a4-1661948616529)]

    十二、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?

    为什么?

    不能向编译后得到的类中增加实例变量;

    能向运行时创建的类中添加实例变量;

    1.因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size

    实例变量的内存大小已经确定,同时 runtime 会调用 class_setvarlayout 或 class_setWeaklvarLayout 来

    处理 strong weak 引用.所以不能向存在的类中添加实例变量。

    2.运行时创建的类是可以添加实例变量,调用 class_addIvar 函数. 但是的在调用 objc_allocateClassPair

    之后,objc_registerClassPair 之前,原因同上.

    [图片上传失败...(image-d0c96d-1661948616529)]

    十四、runtime 如何通过 selector 找到对应的 IMP 地址?

    每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实 selector 本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.、

    十五、runtime 如何实现 weak 变量的自动置 nil?知道 SideTable 吗?

    runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表中。 用 weak 指向的对象内 存地址作为 key,当此对象的引用计数为 0 的时候会 dealloc,假如 weak 指向的对象内存地址是 a,那么就 会以 a 为键, 在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil。

    更细一点的回答:

    1.初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

    2.添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数, objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。

    3.释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

    SideTable 结构体是负责管理类的引用计数表和 weak 表,

    详解:参考自《Objective-C 高级编程》一书

    [图片上传失败...(image-80e7a0-1661948616529)]

    [图片上传失败...(image-232c49-1661948616529)]

    3. 释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

    当 weak 引用指向的对象被释放时,又是如何去处理 weak 指针的呢?当释放对象时,其基本流程如下:

    1.调用 objc_release

    2.因为对象的引用计数为 0,所以执行 dealloc

    3.在 dealloc 中,调用了_objc_rootDealloc 函数

    4.在_objc_rootDealloc 中,调用了 object_dispose 函数

    5.调用 objc_destructInstance

    6.最后调用 objc_clear_deallocating

    对象被释放时调用的 objc_clear_deallocating 函数:

    1.从 weak 表中获取废弃对象的地址为键值的记录

    2.将包含在记录中的所有附有 weak 修饰符变量的地址,赋值为 nil3.将 weak 表中该记录删除

    4.从引用计数表中删除废弃对象的地址为键值的记录

    总结:

    其实 Weak 表是一个 hash(哈希)表,Key 是 weak 所指对象的地址,Value 是 weak 指针的地址(这个地址的值是所指对象指针的地址)数组。

    十六、objc 中向一个 nil 对象发送消息将会发生什么?

    如果向一个 nil 对象发送消息,首先在寻找对象的 isa 指针时就是 0 地址返回了,所以不会出现任何错误。 也不会崩溃。

    详解:

    如果一个方法返回值是一个对象,那么发送给 nil 的消息将返回 0(nil);

    如果方法返回值为指针类型,其指针大小为小于或者等于 sizeof(void*) ,float,double,long double 或

    者 long long 的整型标量,发送给 nil 的消息将返回 0;

    如果方法返回值为结构体,发送给 nil 的消息将返回 0。结构体中各个字段的值将都是 0;

    如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。

    十七、objc 在向一个对象发送消息时,发生了什么?

    objc 在向一个对象发送消息时,runtime 会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的 方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,走消息转发机制,一旦找到 ,就去执行它的实现 IMP 。

    [图片上传失败...(image-94ab67-1661948616529)]

    详解:

    在 isKindOfClass 中有一个循环,先判断 class 是否等于 meta class,不等就继续循环判断是否等于 meta class 的 super class,不等再继续取 super class,如此循环下去。

    [NSObject class]执行完之后调用 isKindOfClass,第一次判断先判断 NSObject 和 NSObject 的 meta class 是否相等,之前讲到 meta class 的时候放了一张很详细的图,从图上我们也可以看出,NSObject 的 meta class 与本身不等。接着第二次循环判断 NSObject 与 meta class 的 superclass 是否相等。还是从那张图 上面我们可以看到:Root class(meta) 的 superclass 就是 Root

    class(class),也就是 NSObject 本身。所以第二次循环相等,于是第一行 res1 输出应该为 YES。

    同理,[Sark class]执行完之后调用 isKindOfClass,第一次 for 循环,Sark 的 Meta Class 与[Sark class] 不等,第二次 for 循环,Sark Meta Class 的 super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。第三次 for 循环,NSObject Meta Class 的 super class 指向的是 NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Class 的 super class 指向 nil, 和 Sark Class 不相等。第四次循环之 后,退出循环,所以第三行的 res3 输出为 NO。

    isMemberOfClass 的源码实现是拿到自己的 isa 指针和自己比较,是否相等。

    第二行 isa 指向 NSObject 的 Meta Class,所以和 NSObject Class 不相等。第四行,isa 指向 Sark 的Meta Class,和 Sark Class 也不等,所以第二行 res2 和第四行 res4 都输出 NO。

    十九、Category 在编译过后,是在什么时机与原有的类合并到一起的?

    1. 程序启动后,通过编译之后,Runtime 会进行初始化,调用 _objc_init。

    2. 然后会 map_images。

    3. 接下来调用 map_images_nolock。

    4. 再然后就是 read_images,这个方法会读取所有的类的相关信息。

    5. 最后是调用 reMethodizeClass:,这个方法是重新方法化的意思。

    6. 在 reMethodizeClass: 方法内部会调用 attachCategories: ,这个方法会传入 Class 和 Category ,会将方法列表,协议列表等与原有的类合并。最后加入到 class_rw_t 结构体中。

    二十、Category 有哪些用途?

     给系统类添加方法、属性(需要关联对象)。

    对某个类大量的方法,可以实现按照不同的名称归类。

    二十一、Category 的实现原理?

    被添加在了 class_rw_t 的对应结构里。

    Category 实际上是 Category_t 的结构体,在运行时,新添加的方法,都被以倒序插入到原有方法列 表的最前面,所以不同的 Category,添加了同一个方法,执行的实际上是最后一个。

    拿方法列表举例,实际上是一个二维的数组。

    Category 如果翻看源码的话就会知道实际上是一个 _catrgory_t 的结构体。

    --

    例如我们在程序中写了一个 Nsobject+Tools 的分类,那么被编译为 C++ 之后,实际上是:

    [图片上传失败...(image-eb2544-1661948616529)]

    二十二、_objc_msgForward 函数是做什么的,直接调用它将会发生什么?

    _objc_msgForward 是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候, _objc_msgForward 会尝试做消息转发。

    详解:_objc_msgForward 在进行消息转发的过程中会涉及以下这几个方法:

    1. List itemresolveInstanceMethod:方法 (或 resolveClassMethod:)。

    2. List itemforwardingTargetForSelector:方法

    3. List itemmethodSignatureForSelector:方法

    4. List itemforwardInvocation:方法

    5. List itemdoesNotRecognizeSelector: 方法

    相关文章

      网友评论

          本文标题:Category解析

          本文链接:https://www.haomeiwen.com/subject/euadnrtx.html