Objective-C Runtime浅析

在初学Objective-C的时候,觉得有很多陌生且奇怪的语法和特性。

比如NSObject *obj = [[NSObject alloc] init];这种语法;比如尝试调用空指针的函数并不会导致crash这种特性。直到有机会深入了解Objective-C Runtime,才多少有了一些理解。

如果你也对这种看似奇怪的写法感到好奇的话,相信这篇文章能够解答你的疑问。

背景

动态编程语言

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically.

Objective-C是一门“动态编程语言”,也就是尽可能把决策推迟到运行时。

什么是Objective-C Runtime

苹果官方文档给出的定义如下:

The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps.

Objective-C的动态性主要体现在如下3个方面 :

  • 动态类型:在运行时才确认对象的类型
  • 动态绑定:在运行时才确定对象的调用方法
  • 动态加载:在运行时才加载需要的资源或代码

runtime就是为Objective-C提供上述动态特性的库,runtime赋予了C语言面向对象的能力。

为什么要了解Runtime

Runtime为Objective-C提供的动态特性,给开发者提供了更多灵活性,这种灵活性可以在解决一些复杂问题时提供更多方案。
Method Swizzling就是一个很好的例子,大家不妨思考一下下面问题的解决方案:

为现有项目中所有view设置统一的背景颜色。(或者更具实际意义的问题,在某些情况下需要将App中所有页面置灰)

一般想到的方案可能是统一继承一个父类,在父类中实现上述要求。基于Runtime的Method Swizzling可以提供更方便的解决方案 。

另外了解Runtime也可以从底层辅助Debug。

总之,Runtime是一个重要知识点,接下来我们一起深入了解Runtime的运行机制。

Runtime

源码面前,了无秘密 ——《STL源码剖析》

对象和类的实现

上面提到Runtime为Objective-C提供动态类型、动态绑定等动态特性,在运行时才确定对象的类型、调用方法等信息。为此,我们首先需要了解Objective-C中对象是如何表示的,源码objc.h 中利用结构体表示对象:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa;
};

可以看出,对象只是对Class(类)的简单封装,我们继续探究Class的含义:

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

objc_class在runtime.h 中给出了定义 :

struct objc_class {
    Class _Nonnull isa;
    Class _Nullable super_class;
    const char * _Nonnull name;
    long version;
    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;
};
/* Use `Class` instead of `struct objc_class *` */

上述结构体也就是Objective-C中类的表示方式,其中比较重要的成员包括:

  • ivars ,表示该Objective-C类的成员变量。

    struct objc_ivar {
        char * _Nullable ivar_name;
        char * _Nullable ivar_type;
        int ivar_offset;
    };
    
    struct objc_ivar_list {
        int ivar_count;
        /* variable length structure */
        struct objc_ivar ivar_list[1];
    };
    
  • methodLists,表示该Objective-C类中的函数

    struct objc_method {
        SEL _Nonnull method_name;
        char * _Nullable method_types;
        IMP _Nonnull method_imp;
    };
    
    struct objc_method_list {
        struct objc_method_list * _Nullable obsolete;
        int method_count;
        /* variable length structure */
        struct objc_method method_list[1];
    };
    
  • cache,用于缓存函数,提升性能。关于cache在性能优化方面的作用,美团技术团队的文章《 深入理解 Objective-C:方法缓存》值得一看 。

    struct objc_cache {
        unsigned int mask /* total = mask + 1 */;
        unsigned int occupied;
        Method _Nullable buckets[1];
    };
    
  • protocols:该Objective-C类中的协议(链表)

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

对象、类和元类

从上述源码中可以看到,对象结构体中只有一个类结构体指针,对象可以通过该指针找到与之对应的类,进而可以在类结构体中找到成员变量、成员函数等信息。
在这里插入图片描述

类结构体中还包含两个类结构体指针:

  • isa
  • super_class

super_class比较容易理解,是指向父类的指针,利用该指针可以实现继承。

isa指针存在的作用又是什么呢?个人理解,一个重要作用是实现类函数

In Objective-C, a class is itself an object with an opaque type calledClass. Classes can’t have properties defined using the declaration syntax shown earlier for instances, but they can receive messages.

类函数,顾名思义就是类拥有的函数。如果类本身也是一个对象的话,只要找到它对应的类结构体(meta-class)的话,就可以找到类函数了。
在这里插入图片描述

类这个“对象”对应的类,称为元类(meta-class),每个类都有一个与之对应的元类。
这样设计虽然方便,但也带来了一些问题,比如元类中isa指向哪里?元类的super_class指向哪里?
下图给出了答案:
在这里插入图片描述

  • 元类的isa指向哪里?
    所有元类的isa指针都指向基类的元类。如果一个类没有父类,其元类就指向自身。
  • 元类的super_class指向哪里?
    类对应元类的super_class指向该类父类的元类。

函数调用

In Objective-C, messages aren’t bound to method implementations until runtime. The compiler converts a message expression [receiver message] into a call on a messaging function,objc_msgSend. This function takes the receiverand the name of the method mentioned in the message—that is, the method selector—as its two principal parameters objc_msgSend(receiver, selector). Any argumentspassed in the message are also handed toobjc_msgSend objc_msgSend(receiver, selector, arg1, arg2, …)

我们知道,在Objective-C中,[object methodName]表示调用对象objectmethodName方法。和C语言中直接按函数地址取用不同,Objective-C中的函数调用是通过Runtime中的objc_msgSend()实现的,也就是会将[object methodName]翻译成objc_msgSend(id self, SEL op, ...)

也就是说,Objective-C将函数调用,转化成了消息的传递,也正是这种转化,造成了文章开头提到的,尝试调用空指针函数不会导致crash的反常现象。
调用某个对象的函数,就是给对象发送消息,消息中携带的SEL可以将其理解为方法的ID,通过SEL可以在objc_class中的methodLists查找到方法的具体实现,进而执行。
在这里插入图片描述

如果在类中没有找到该函数,会通过类的super_class指针,去父类中查找,如图所示,循环往复直到基类。
在这里插入图片描述

如果基类中也没有找到,该消息就会被丢弃,但不会引发崩溃。

  • 类函数调用
    上面提到过,Objective-C中类也是对象,对应元类中存储的就是类函数。所以类函数的调用,就是通过元类查找并执行。

到这里,我们对Objective-C的类和函数调用有了浅显的理解,现在看文章开头提到的语法NSObject *obj = [[NSObject alloc] init];,你有没有自己的理解呢?

[NSObject alloc]是给NSObject发送消息,也就是调用它的alloc方法:

Returns a new instance of the receiving class.

根据官方文档,作用是得到类NSObject的实例。然后调用这个实例的初始化函数init

参考

  1. Objective-C Runtime Programming Guide https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
  2. iOS程序员面试笔试宝典 https://weread.qq.com/web/bookDetail/338322907192ea37338e0dc
  3. Method Swizzling: What, Why & How https://medium.com/@grow4gaurav/method-swizzling-what-why-how-cdbcdff98141
  4. https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc.h#L41
  5. https://github.com/apple-oss-distributions/objc4/blob/rel/objc4-781/runtime/runtime.h#L55
  6. https://github.com/apple-oss-distributions/objc4/blob/rel/objc4-781/runtime/runtime.h#L1954
  7. 深入理解 Objective-C:方法缓存 https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html
  8. Programming with Objective-C https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#:~:text=In%20Objective%2DC%2C%20a%20class,but%20they%20can%20receive%20messages
  9. Objective-C Runtime Programming Guide https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html#//apple_ref/doc/uid/TP40008048-CH104-SW1
  10. alloc https://developer.apple.com/documentation/objectivec/nsobject/1571958-alloc?language=objc
  11. init https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值