经过源码分析isa

通篇以一下代码为例objc4-781源码中验证对应方法ios

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


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

@implementation TDPerson
@end

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

复制代码
  • clang

    • 概念

    Clang是⼀个C语⾔、C++、Objective-C语⾔的轻量级编译器。源代码发布于BSD协议下。c++

    Clang将⽀持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
    **Clang是⼀个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器 **
    2013年4⽉,Clang已经全⾯⽀持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下⼀个⼩更新版本)Clang将⽀持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。 [2] Clang是⼀个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语⾔规范⼏乎彻底兼容(固然,也有部分不兼容的内容, 包括编译命令选项也会有点差别),并在此基础上增长了额外的语法特性,⽐如C函数重载 (经过_ attribute_((overloadable))来修饰函数),其⽬标(之⼀)就是超越GCC。算法

    • 将oc文件编译成c++文件的编译指令
      1. clang -rewrite-objc main.m -o main.cpp
      2. clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk main.m
      3. xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m -o main-x86_64.cpp(模拟器)
      4. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp(手机)
  • 对象的本质

    1. 将.m文件编译成.cpp文件并全局搜索TDPerson可获得

    发现对象在底层会被编译成结构体 2. NSObject_IMPL探索
    从上述编译后的代码能够发现结构体继承NSObject_IMPL(这里的继承是伪继承,这种写法能够达到继承的效果,TDPerson_IMPL结构体能够拥有NSObject_IMPL的全部属性)NSObject_IMPL里面又是什么呢继续在编译后的文件中找 因此TDPerson_IMPL中的第一个属性 NSObject_IVARS 其实就是NSObject中的isamarkdown

  • isa

    经过上述论证发现对象的本质就是结构体,而且该结构的第一个属性是isa,isa又是什么架构

    1. 根据源码查找isa

    根据alloc & init & new 源码分析文章中看alloc过程会发现initIsa的方法发现isa,是一个isa_t类型,继续跟源码看一下isa_t又是什么发现是一个联合体位域,因此isa其实就是一个联合体位域 2. isa为何是一个联合体位域 * 联合体属性公用一片内存,成员之间是互斥的,这里有两个属性cls、bits,这样写增长了isa_t的灵活性,能够经过cls初始化isa,同事也能够经过bits初始化isa * 位域:咱们知道主要是用来节省空间的,就是有些类信息其实不须要那么多位数去存储可能只须要一位就够了,使用位域咱们就能够节省很多空间,下图是ISA_BITFIELD属性占用内存状况: 能够看到不少属性只须要1位就够了 3. isa中的属性分别表明什么 * 属性含义 * 属性内存占用状况图解 4. 透过源码查看isa初始化流程(initIsa)
    经过alloc & init & new 源码分析文章能够知道alloc方法会走到一个加initIsa的方法 5. 经过源码验证isa的内存分配状况 1. initIsa源码 ``` inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { ASSERT(!isTaggedPointer());app

    //从这里就能够验证isa为何用联合体
        //若是仅仅就是一个存isa指针咱们只须要经过赋值cls初始化就好
        //若是含有一些类信息、引用计数等,就须要赋值bits来初始化isa
        if (!nonpointer) {
            //若是是纯指针,直接设置到cls
            isa = isa_t((uintptr_t)cls);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
            //初始化isa
            isa_t newisa(0);
      #if SUPPORT_INDEXED_ISA
            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
            //标记空间被初始化
            newisa.bits = ISA_MAGIC_VALUE;
            // 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;
      #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;
        }
      }
    
     ```
     2. **isa_t newisa(0);**以后打印看结果![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0482e167bc36440a9508e3da919df4fd~tplv-k3u1fbpfcp-zoom-1.image)
     3. **newisa.bits = ISA_MAGIC_VALUE**以后再看结果
     4. 对比第二三步得出结论  
        1. **ISA_MAGIC_VALUE**是就是给nonpointer以及magic赋值的
        2. 怎么赋值的为何nonpointer是1,magic是59  
        首先透过源码仔细看一下**ISA_MAGIC_VALUE**究竟是什么![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c685d55cff28435998cb49d933e40b4c~tplv-k3u1fbpfcp-zoom-1.image)发现就是一个宏,而后这个宏是一个16进制的值**0x001d800000000001**,下一步再将这个16进制的值转换成二进制![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a606bac1df454708bb22c2231ef5b831~tplv-k3u1fbpfcp-zoom-1.image)从上面isa的属性内存分布图不难看出nonpointer存储在第0位置的因此是1毋庸置疑,而后咱们项目是跑在mac上的不是真机,因此**CPU架构师x86_64**,因此呢shiftcls就占用了44位,加上前面三位(nonpointer、has_assoc、has_cxx_dtor各占用1位)而后又是从0位置开始得出magic从47位开始读,而后magic又是占用6位因此获得magic=111011,而后再转换成10进制获得是59
        2. 验证了联合体的特性bits、cls互斥公用一段内存上图中8303511812964353转换成16进制其实就是0x001d800000000001,因此这两个属性值同样![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/975e363198f048749f1d2767b9302c2b~tplv-k3u1fbpfcp-zoom-1.image)![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60e08999e7694ac6aa47937ee7d5d8ce~tplv-k3u1fbpfcp-zoom-1.image)
     5. 细节分析**newisa.shiftcls = (uintptr_t)cls >> 3;**  
     应为赋值会从第0位置开始读取数据,然而cls中的shiftcls信息是从第三位开始存储的,因此要右移三位,还一个就是shiftcls赋值为何要强转:由于内存的存储不能存储字符串,机器码只能识别 0 、1这两种数字,因此须要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long
    复制代码
    1. isa与类的关联

    上面的代码继续往下执行**newisa.shiftcls = (uintptr_t)cls >> 3;**以后在打印newisa看结果 发现此时cls就是TDPerson,这个时候再将newisa赋值给isa那么isa就和类关联起来了iphone

  • 经过isa获取类信息

    1. 经过x/4gx 查看内存分配状况

    应为isa是全部对象的第一个参数,因此就能够获得isa的指针地址0x001d8001000020e9 2. 第一种方法经过isa & ISA_MASK获得类其实& ISA_MASK就是一个小算法的到shiftcls值,应为isa里面只有shiftcls存储的才是类的指针 3. 经过位运算
    想要获取类信息其实就是获取isa里面的shiftcls信息,因此要想获取到shiftcls信息确定要将其余位置的信息剔除掉,上文说过shiftcls无论在arm_64仍是x86_64的架构中都是冲第3各位值存储的只不过shiftcls占用的值不同,因此在arm_64位的系统中须要剔除前三位和后面28位的信息,而在x86_64的架构中须要剔除前三位和后面17位的信息,最重要的已不是要将shiftcls相对位置还原到初始的位置,而后在打印大概流程以下 4. 经过object_getClass获取
    其实object_getClass源码实现就是isa & ISA_MASK 函数

相关文章
相关标签/搜索