美文网首页
runtime 小结

runtime 小结

作者: li_c297 | 来源:发表于2018-10-10 20:14 被阅读0次

isa
是一个objc_class类型的指针.
根据上面内存布局以一个objc_class指针为开始的所有都可以当做一个object来对待!

id
可以看到,iOS中很重要的id实际上就是objc_object的指针.而NSObject的第一个对象就是Class类型的isa。因此id可以标示所有基于NSObject的对象。

typedef struct objc_class *Class;  
typedef struct objc_object {  
    Class isa;  
} *id;  

Class

struct objc_class {  

    Class isa;  

#if !__OBJC2__  

Class super_class                                        OBJC2_UNAVAILABLE;/*父类*/  

const char *name                                         OBJC2_UNAVAILABLE;/*类名称*/  

long version                                             OBJC2_UNAVAILABLE;/*版本信息*/  

long info                                                OBJC2_UNAVAILABLE;/*类信息*/  

long instance_size                                       OBJC2_UNAVAILABLE;/*实例大小*/  

struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;/*实例参数链表*/  

struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;/*方法链表 */  

struct objc_cache *cache                                 OBJC2_UNAVAILABLE;/*方法缓存*/  

struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;/*协议链表*/  

#endif  

} OBJC2_UNAVAILABLE; 

<small>
name:一个 C 字符串,指示类的名称。我们可以在运行期,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))或该类的 metaclass(id objc_getMetaClass(const char *aClassName));

version:类的版本信息,默认初始化为 0。我们可以在运行期对其进行修改(class_setVersion)或获取(class_getVersion)。

instance_size:该类的实例变量大小(包括从父类继承下来的实例变量);

ivars:指向 objc_ivar_list 的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为 NULL;

methodLists:与 info 的一些标志位有关,CLS_METHOD_ARRAY 标识位决定其指向的东西(是指向单个 objc_method_list还是一个 objc_method_list 指针数组),如果 info 设置了 CLS_CLASS 则 objc_method_list 存储实例方法,如果设置的是 CLS_META 则存储类方法; 我们可以动态修改 *methodList 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因。这里可以参考下美团技术团队的文章:深入理解 Objective-C: Category

cache:指向 objc_cache 的指针,用来缓存最近使用的方法,以提高效率;

protocols:指向 objc_protocol_list 的指针,存储该类声明要遵守的正式协议。
</small>
Ivar
表示成员变量的类型。

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

其中 ivar_offset 是基地址偏移字节

Method
Runtime内部定义的方法,Class中定义有一个objc_method_list,链表都是objc_method类型的

typedef struct objc_method *Method;  
struct objc_method {  
    SEL method_name                                          OBJC2_UNAVAILABLE;/*标示方法名称*/  
    char *method_types                                       OBJC2_UNAVAILABLE;/*方法的参数类型*/  
    IMP method_imp                                           OBJC2_UNAVAILABLE;/*指向该方法的具体实现的函数指针*/  
}  
      
struct objc_method_list {  
    struct objc_method_list *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;  
}  

SEL

typedef struct objc_selector     *SEL;

IMP

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

Cache

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

消息
下图详细叙述消息发送的步骤:

14391243-b7e41af0b625f59f.gif
<small>
1.首先检测这个 selector 是不是要忽略。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数。
2.检测这个 selector 的 target 是不是 nil,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。
3.如果上面两步都通过了,那么就开始查找这个类的实现 IMP,先从 cache 里查找,如果找到了就运行对应的函数去执行相应的代码。
4.如果 cache 找不到就找类的方法列表中是否有对应的方法。
5.如果类的方法列表中找不到就到父类的方法列表中查找,一直找到 NSObject 类为止。
6.如果还找不到,就要开始进入动态方法解析了,后面会提到。
</small>

objc_msgSend有以下伪代码

id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

可见伪代码中我们看到class_getMethodImplementation(Class cls, SEL sel) 方法用来寻找IMP地址,class_getMethodImplementation的实现可参考:https://blog.csdn.net/dp948080952/article/details/52563064

当 objc_msgSend 找到方法对应实现时,它将直接调用该方法实现,并将消息中所有参数都传递给方法实现,同时,它还将传递两个隐藏参数:
接受消息的对象(self 所指向的内容,当前方法的对象指针)
方法选择器(_cmd 指向的内容,当前方法的 SEL 指针)

动态方法解析
当 Runtime 系统在 Cache 和类的方法列表(包括父类)中找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod: 或 resolveClassMethod: 来给我们一次动态添加方法实现的机会

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

重定向
消息转发机制执行前,Runtime 系统允许我们替换消息的接收者为其他对象。通过 -(id)forwardingTargetForSelector:(SEL)aSelector 方法

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

转发
当动态方法解析不做处理返回 NO 时,则会触发消息转发机制。这时 forwardInvocation: 方法会被执行,我们可以重写这个方法来自定义我们的转发逻辑,唯一参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation: 方法来对不能处理的消息做一些处理。也可以将消息转发给其他对象处理,而不抛出错误。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}
1330553-400bc5bde4db1725.png

健壮的实例变量(Non Fragile ivars)
在 Runtime 的现行版本中,最大的特点就是健壮的实例变量了。当一个类被编译时,实例变量的内存布局就形成了,它表明访问类的实例变量的位置。实例变量一次根据自己所占空间而产生位移:

1330553-bcbc243a281ef8d9.png
上图左是 NSObject 类的实例变量布局。右边是我们写的类的布局。这样子有一个很大的缺陷,就是缺乏拓展性。哪天苹果更新了 NSObject 类的话,就会出现问题:
1330553-33263710847f6d86.png
我们自定义的类的区域和父类的区域重叠了。只有苹果将父类改为以前的布局才能拯救我们,但这样导致它们不能再拓展它们的框架了,因为成员变量布局被固定住了。在脆弱的实例变量(Fragile ivar)环境下,需要我们重新编译继承自 Apple 的类来恢复兼容。在健壮的实例变量下,编译器生成的实例变量布局跟以前一样,但是当 Runtime 系统检测到与父类有部分重叠时它会调整你新添加的实例变量的位移,那样你再子类中新添加的成员变量就被保护起来了。我们让自己的类继承自 NSObject 不仅仅是因为基类有很多复杂的内存分配问题,更是因为这使得我们可以享受到 Runtime 系统带来的便利。

参考引用见:
https://www.cnblogs.com/ioshe/p/5489086.html
https://blog.csdn.net/dp948080952/article/details/52563064
https://blog.csdn.net/qq_32744055/article/details/52387079!

相关文章

  • Runtime小结

    一、runtime简介 RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消...

  • runtime 小结

    isa是一个objc_class类型的指针.根据上面内存布局以一个objc_class指针为开始的所有都可以当做一...

  • Runtime小结

    在runtime中一个对象就是用结构体来表示的 runtime中的表示 获取类的属性列表 获取类的成员变量 获取类...

  • Runtime小结

    一、Runtime简介RunTime简称运行时,是一套底层的 C 语言 API。Objective-C是一门动态编...

  • runtime 小结

    OC被称之为动态运行时语言,最主要的原因就是因为两个特性,一个是运行时也就是runtime,一个是多态。 runt...

  • Runtime学习小结

    感觉最近记忆越来越差,所以记录一下自己学习的东西 本文用于自己学习记录,文章参考http://www.cocoac...

  • 【iOS小结】Runtime

    一.Runtime简介 C语言中,在编译期,函数的调用就会决定调用哪个函数。而OC的函数,属于动态调用过程,在编译...

  • Runtime使用小结

    OC方法的本质: Runtime技术的使用基于OC是一门动态语言,那么何为动态语言呢?动态语言意味着变量类型的确认...

  • Runtime个人小结

    runtime往深了研究很复杂,我就学了点皮毛,给自己做个比较,有不对的地方欢迎指出 runtime的作用: 1....

  • runtime编程指南小结

    本章节作为Objective-C 2.0运行时系统编程指南的小结;也算是一次对系统性书籍的知识吸收,对零散知识的复...

网友评论

      本文标题:runtime 小结

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