iOS 大师养成之路--对象的本质

1,482 阅读6分钟

一、对象到底是什么?

我们的Xcode其实很强大,集成了很多牛逼的工具,clang就是其中之一,我们可以使用以下的命令对.m文件进行编译我们就可以看到我们的OC 编译到底层的C或者C++到底是怎么样的

我这里的例子是使用的是main.m 文件,各位可以根据具体SDK的文件路径和需要编译的文件去编译研究
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m  //模拟器的使用命令
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk  main.m //真机的使用命令

我们编译完看到的就是一个C++的结构体,没错就是一个objec_class的结构体,他的结构体长成这个样子:

当然下面还有很多的函数,太长了,不浪费地方了。我们发现有些有意思的问题:

  • isa 为什么没有? --> 在父类的结构体里面,直接继承
  • 它还是继承之objc_object? -->

  • 我的对象呢为什么只有一个isa 指针?--> 其实这里我只能这么解释下,可能不是很专业但是意思能表达清楚。在编译阶段,所有的类都有自己的内存空间了,相当于已经建好统一的毛坯了,只是要如何装修每个对象有每个对象的想法。这样一来对象实例化话时就很快了只要按照自己的想法直接装修好就行,再贴上自己的名字标示所有权。

二、神秘的isa

在上面我有说到对象在实例化时返回的就是一个isa,而且就干了一件事情,就是把我们实例化出来的对象跟类进行绑定。那么isa就是是个啥玩意?且看以下分解:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;//类对象
    uintptr_t bits;//位域
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
  • 我们看到其实就是一个联合体,就可能是bits 也可能是cls.-->这也进一步说明对象的本质。
  • 那么要是bits还能怎么绑定类呢?我们再看看bits都有啥
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \表示是否对 isa 指针开启指针优化
      uintptr_t has_assoc         : 1;                                       \是否有关联对象
      uintptr_t has_cxx_dtor      : 1;                                       \否有 C++ 或者 Objc 的析构器
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \存储类指针的值
      uintptr_t magic             : 6;                                       \用于调试器判断当前对象是真的对象还是没有初始化的空间
      uintptr_t weakly_referenced : 1;                                       \是否被指向或者曾经指向一个 ARC 的弱变量
      uintptr_t deallocating      : 1;                                       \是否正在释放
      uintptr_t has_sidetable_rc  : 1;                                       \当对象引用技术大于 10 时,则需要借用该变量存储进位
      uintptr_t extra_rc          : 19\当表示该对象的引用计数值,实际上是引用计数值减 1,当大于10时需要借用上面的标志位

这是一个宏定义的位域,后面的数字表示占有的位数。其实就是对isa进行了优化,把一个8字节的空间进行了高效使用,从而存储了更多的东西,如刚刚的cls 就存储在shiftcls中。

isa走位

相信有很多人已经有所了解,但是说到isa,我还是介绍下走位

  • 实例化对象的isa -> 类, 类的isa -> 元类,元类的isa -> 根元类,根元类的isa -> 根元类自己。流程大概就是如此,如果是NSObject的实例化对象会少一次。
  • 附上isa走位图
  • isa走位图解读,虚线表示的是isa走位,实线表示的是superclass的继承关系。rootClass是NSObject,万物皆对象

三、cache是什么?

字面意思就是缓存,事实上Runtime进行消息查找的时候如果每次查完就直接扔了很浪费,哪些经常用的或者最近查找过的方法会进行缓存,这样就提高了方法调用的效率。具体介绍可以参考我之前写过的关于cache_t的分析的文章 https://juejin.cn/post/6844904040153743368

四、bits 是什么?

不知道我们是不是很好奇,既然对象实例化之后啥都没有那么我写的那些属性、方法都在哪呢?难道在类里面吗? 还真是,不过这个类是相对的,类是对象的类,元类是类的类,以此类推。bits里面就存了对象的属性、实例化方法(注意是实例化方法,类方法在元类的bits里面

1、bits 结构体

我们看到了bits.data()返回的是一个class_rw_t *结构体指针,说明了这个bits里面存的就是这个结构体数据。

  • rw意味着什么,可读可写区域?谁写?肯定不是我们普通iOS开发人员写进去的,是编译器在编译、运行阶段对代码中的类、对象的解析翻译,把方法、属性、协议写入到这么一个可读可写的区域。
  • 全部都写在这里吗?不是。class_ro_t,read only还有一个只读区域
  • 那rw里写的是啥?类别、类拓展中的方法、属性、协议。
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
  • class_ro_t 结构体介绍,从下面的结构体我们可以看到有方法数组、协议数组、属性数组,但是还有个ivars,我们给类添加属性时,系统会默认实现set、get方法,同时还生成一个对应的成员变量。这个成员变量就存在ivars里。
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

2、实际看看验证下

我们来先自定义一个类,配置好objc的源码,用LLDB调试看看内存中的数据

  • 准备好一个自定义的类,在main函数中直接调用

  • 获取bits中的数据

  • ro中的数据

  • 读属性,nickName 已经在数组中的第一个,总数也是一个(我们的类中也只写了一个)。

  • 读方法,属性对应的set、get方法,我们自己定义的一个实例化方以及一个c++的函数

  • 读成员变量,hobby以及属性自动生成的_nickName都在这里了

五、写在最后的感想

在这些底层的研究过程中,思考苹果开发工程师的开发思路和设计思想是一件很享受的事情,弄清楚他们怎样做到让这个代码的效率如此之高对自己的开发水平提高有很好地帮助,用C++ 写过游戏的朋友估计体验比较深刻,游戏所有的都是自己实现,除了场景以及渲染用到的游戏引擎。

有兴趣一起交流的朋友,可以加 QQ:578200388