OC底层原理 06: isa结构分析

2020-09-09  本文已影响0人  花白少年梦

主动已经是我对热爱东西表达的极限了

对象的本质?
联合体位域的简析?
isa的结构信息?
isa如何关联类?
通过位运算验证关联类
总结。

Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器

Clang 通过底层编译,将一些m文件编译为cpp。 因为OC为C++或者C的超集,通过Clang底层编译,可以更多的看到底层的实现原理与逻辑和底层的架构

Clang终端操作四种命令如下:

clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件

`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 (手机)

Clang 具体操作请查看:后续

通过Clang编译查看的底层是如何实现的,首先定义如下:

  @interface LGPerson : NSObject
  @property (nonatomic, copy) NSString *name;
  @end

  @implementation LGPerson
  @end

  int main(int argc, const char * argv[]) {
      @autoreleasepool {
          // insert code here...
          NSLog(@"Hello, World!");    }
      return 0;
  }

找到已经编译好的main.cpp文件,搜索LGPerson,得到我想要的信息如下:

  #ifndef _REWRITER_typedef_LGPerson
  #define _REWRITER_typedef_LGPerson
  typedef struct objc_object LGPerson;
  typedef struct {} _objc_exc_LGPerson;
  #endif
  
  extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
  struct LGPerson_IMPL {
          struct NSObject_IMPL NSObject_IVARS;
      NSString *_name;
  };

  // @property (nonatomic, copy) NSString *name;
  /* @end */
   // @implementation LGPerson
  static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { 
        return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); 
  }

  extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

  static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
      objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
  }
  // @end

对象是如何被编译为结构体的呢?

我们知道结构体在C++可以继承,C可以伪继承

伪继承方式:
直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有 NSObject中的所有成员变量
LGPerson中的第一个属性 NSObject_IVARS 等效于 NSObject中isa

这里将LGPerson_IMPL结构体中的第一属性命名为当前结构体,即LGPerson_IMPL拥有 NSObject中的所有成员变量。而NSObject_IVARSisa

LGPerson类中的nameget, set方法也是一一对应的:

main.cpp中类对应的get,set方法截图

set方法调用了objc_setProperty,通过分析得出objc_setProperty采用工厂模式:return newValue, remove oldValue ;意味着任何类中的set方法都会执行了如下操作:

objc4_781源码中找到objc_setProperty查看如何执行操作get , set

继续分析isa结构,OC底层原理 结构体&联合体

在之前探索 OC底层原理 alloc & init & new 篇 时提到过initInstanceIsa方法,通过initInstanceIsa为切入点来分析isa是如何初始化的,可以看到isa指针的类型isa_t定义是通过联合体(union)来定义联合体不清楚的小伙伴请参考:OC底层原理 结构体&联合体

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

    Class cls; //这里返回的cls 为什么会是一个class类型?请查看后续
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

这里的isa_t为什么要定义成联合体呢?是为了优化内存,而isa指针占用的内存大小为8字节,即64位,已经能够存储大量信息了,这样可以节省内存空间,以提高性能的目的

isa_t的定义中可以看出,
通过cls初始化,bits无默认值
通过bits初始化,cls有默认值
体现了联合体互斥性

通过宏ISA_BITFIELD可以看出isaiOS( __arm64__)macOS(__x86_64__)的计算是存在差异

isa不同环境的差异化截图

更直观的isa的结构信息图如下:

isa结构对照表

通过 initIsa方法,查看 isa指针是如何被初始化

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);//isa初始化
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);  //isa初始化
#if SUPPORT_INDEXED_ISA  // !nonpinter 执行,即isa通过cls定义
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else // bits 时执行的流程
        newisa.bits = ISA_MAGIC_VALUE; // bits进行赋值
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

执行代码,开始验证,以LGPerson为例:

对赋值newisa.bits赋值之后打印出newisa查看LLDB结果

p newisa 打印结果

通过LLDB打印结果,在进行赋值的时候,bits中的magic会存在一个59呢?
使用计算器查看宏ISA_MAGIC_VALUE592进制

为什么magic为59

magic的占位为6,从47~52表示magic的占位,所以magic的默认值为59二进制表示;也可以理解为isa指针将magic的占位为6,转换为2进制存储

当执行完如下代码后,LLDB打印出当前newisa
clsisa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来。而newisa.shiftcls = (uintptr_t)cls >> 3 是将当前cls 强转为系统能够编译的01识别码。然后右移3位。之所以要移动3位,要知道shiftcls才是我们需要存储的类信息,从上面isa对照表中可以看出,前面三位并不是我们需要的类内容,需要右移3位进行抹0操作。

newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联
isa与类关联结果图

这个时候我们已经知道isa 进行了关联。

initInstanceIsa方法返回出去,然后开始验证:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

我们进入getIsa方法继续查看,

inline Class 
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA(); //直接return了当前isa

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

继续查看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); //这里与我们方式1做的操作是一样的
#endif
}

我们可以看出object_getClass的内部实现,也是采用了位运算&的方式对当前类进行处理。

验证总结:验证isa与类是否关联还有其他方式,这里不一一阐述,有兴趣的同学可以自己探索。

上一篇 下一篇

猜你喜欢

热点阅读