zoukankan      html  css  js  c++  java
  • OC 底层探索 03、对象本质 & isa结构

    本文开始探索 OC 对象的本质是什么?

    一、对象 

    对象的本质 - 结构体

    1、编译后的对象

    1、我们在 main.m 文件中做简单代码如下:

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    @interface MyPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation MyPerson
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }

    clangmain.m 文件编译为 C++ 文件 main.cpp,命令如下:

    我们可从生成的 .cpp 文件中找到关于 MyPerson 的编译后的结构:

     

    MyPerson 编译成了 struct MyPerson_IMP {...} 

    即:对象 --> 在底层编译成了结构体 struct

    问:.cpp文件中第112268行  ‘struct NSObject_IMPL NSObject_IVARS;’ 是什么呢?

    伪继承。在C++ 中结构体是可以继承的,在C中可以也可以伪继承,如上图代码:将继承结构体作为当前结构体的第一个元素。此时,结构体 MyPerson_IMP 拥有 结构体 NSObject_IMPL 的所有成员变量。

    任何类里面都有 NSObject_IVARS ?--> isa

    NSObject_IMPL 结构体:

    struct NSObject_IMPL {
        Class isa;
    };

    2、同样在 .cpp 文件中也可以找到 MyPerson 的 name 属性

    setter/getter 方法

    objc_setProperty(),我们通过 objc 源码跟踪进去具体实现:objc_setProperty() --> reallySetProperty()

    从源码可以看出,setter 方法的操作可以简单理解为就是:新值 retain旧值 release .但需要注意代码中逻辑判断,当 copy 等属性为true时是不同操作。详细讲解 链接.

    在这里我们可以考虑,我们所有的对象的 setter 方法,都是经历一个 retain release 过程,完全是统一的过程。那么这里我们就考虑到了将其进行类似工厂类的封装,所有的对象无需每个都分开处理,我们把它扔进来即可。即:无论外部对象如何变化,每个对象的 setter 方法都会走到  objc_setProperty() --> 一种思路。

    Clang 

    Clang 是什么?

    Clang 是一个C语言、C++、Object-C 语言的轻量化编译器。它是由苹果主导编写 基于 LLVM 的 C/C++/OC 编译器。早起苹果编译器是GCC。

    Clang 常用命令

    // 把目标文件 main.m 编译成c++文件
    clang -rewrite-objc main.m -o main.cpp 
    
    // 若 编译时 UIKit 报错 --> 找到路径
    clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
    
    // `xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些
    xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp // (模拟器)
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp // (真机)

    二、联合体位域

    结构体(struct):结构体中所有变量是共存的,且大小至少是所有变量所占内存的的和。

      优点:变量共存,有容乃大;

      缺点:对内存的分配是粗略的,存在空间浪费的。

    联合体(union):联合体中,变量是互斥关系,即同一时间只能有一个变量。

      优点:内存使用更精细灵活,节省内存空间;

      缺点:互斥性,只有一个变量存在。

    示例:

    位域:变量每一位所代表的信息。

    一字节的分配

    联合体位域大大的优化节省了内存空间

    三、isa 结构

    isa 指针 8 字节64 位,可存储信息:264 ,完全足够我们的地址存储了。

    通过之前 alloc 流程探索我们可知,对象与 isa 关联是 initInstanceIsa() 方法,我们通过源码跟踪进去:

    initInstanceIsa --> initIsa() --> isa_t

    联合体 isa_t :

    union isa_t {
      // 2个初始化 isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits;// cls bits 两个互斥的变量
    #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif };

    ISA_BITFIELD

    - 手机端:

    - Mac OS:

    以 arm64 为例,位域代表含义:

    nonpointer -- 占据第0位,是否对 isa 开启指针优化。0:纯isa 指针;1:不只是类对象地址,isa 中包含了类信息、对象的引用计数等。一般我建的类都是 nopointer 的。

    shiftcls -- 占据第3~35位,为存储类指针的值。开启指针优化的情况下,在 arm64 架构中有33 位用来存类指针。

    has_sidetable_rc -- 为 1 时,则表明有外部挂的引用计数 sideTable,下面的 extra_rc 存引用计数不够用了。

    extra_rc -- 引用计数值-1的 值。例:对象的引用计数为10,则 extra_rc = 9;若 extra_rc 不足以保存引用计数,则上面的 has_sidetable_rc 为true,使用sideTable 管理。

    2、验证 isa 关联

    1:源码跟踪 验证 (本质也是位移操作)

    执行可编译源码工程进入 initIsa() 函数,cls 为 MyPerson,nonpointer = ture:

    newisa.bits = ISA_MAGIC_VALUE;// 赋初值 

    migic = 59 验证:

    打开系统编程计算器:

    二进制 110111 -->  10进制 59

    执行 newisa.shiftcls = (uintptr_t)cls >> 3;

    cls --> MyPerson --> 强转为 uintptr_t 类型 --> 值:4294975728,为何?

    计算机是不识别字符串的,需要将其转成可识别的二进制的 010101......的机器指令。

    右移3位 >>3: shiftcls 从第3号位开始,之前 0 1 2位有三个变量,我们取 shiftcls 时不能受其影响,故做 >>3 抹零操作。 

    继续运行,回到 _class_createInstanceFromZone() 函数。

    我们继续验证。

    isa 地址 & ISA_MASK

    isa 地址 & ISA_MASK --> MyPerson

     

    提一下 ISA_MASK:0x00007ffffffffff8ULL --> 第3~47 位为1,&运算即两头抹零。

    2:runtime - object_getClass() 验证

    跟踪进入 object_getClass() 方法 --> getIsa() --> ISA(): 

    inline Class 
    objc_object::ISA() 
    {
        ASSERT(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;#else
        return (Class)(isa.bits & ISA_MASK);// shiftcls
    #endif
    }

    3:手动位运算验证

    如下图:

     

    补图中漏写文字:$11 = 4294975728 10进制 --> 16进制 0x00000001000020f0

    p/u cls
    (Class) $15 = 4294975728 MyPerson // 等于它

    验证 over。 

    NSObject 中 Class isa:

    NSObject 中 class 对象,我们拿到的是 class 对象,其实是通过 isa_t 拿到的,只是做了处理转化成了class。NSObject 中代码:

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }

    拿到 class 的过程:NSObject --> Class isa --> object_getclass --> ISA():

    return 的是有做 Class 强转的 -->  NSObject 中 Class isa OBJC_ISA_AVAILABILITY;

  • 相关阅读:
    Python使用 odbc、jdbc与 Object Relational Mapping (ORM)进行数据库开发
    Tensorflow安装
    学生作业
    大学课程推荐
    人工智能的开发工具
    android开发
    jdbc-odbc桥
    开博随记
    利用jq实现自适应边缘情况的气泡Tip
    一篇完整的FlexBox布局指南
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13664212.html
Copyright © 2011-2022 走看看