美文网首页
iOS - Isa结构分析

iOS - Isa结构分析

作者: Gumball_a45f | 来源:发表于2020-10-26 15:53 被阅读0次

本文的主要目的是理解类与isa是如何关联的

首先了解一下OC对象的本质是什么

  • Clang clang是一个由Apple主导编写,基于LLVMC/C++/OC的编译器
  • 主要是用于底层编译,将一些文件输出成c++文件,例如main.m输出成main.cpp,其目的是为了更好的观察底层的一些结构及实现的逻辑,方便理解底层原理。
  • 在main中自定义一个类LGPerson,有一个属性name
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end
  • 通过终端,利用clangmain.m编译成main.cpp,有以下几种编译命令
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
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 ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 
  • 打开main.app,发现 LGPerson在底层会被编译为struct结构体
    • LGPerson_IMPL中的第一个属性 其实就是isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有NSObject中的所有成员变量。
//NSObject的定义
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

//NSObject 的底层编译
struct NSObject_IMPL {
    Class isa;
};

//LGPerson的底层编译
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 等效于 Class isa;
    NSString *_name;
};

这里产生了一个疑问,为什么isa的类型是class

  • 这个答案在之前的initInstanceIsa方法中可以得出。其根本原因是由于isa 对外反馈的是类信息,为了让开发人员更加清晰明确,需要在isa返回时做了一个类型强制转换,类似于swift中的 as 的强转。

总结

  • OC对象的本质其实就是结构体
  • LGPerson中的isa是继承自NSObject中的isa

objc_setProperty源码探索

  • 【1】在main.app中除了LGPerson的底层定义,还有属性name对应的getset方法,set方法的实现是依赖于runtime中的objc_setProperty
  • 【2】在objc-781中搜索进入objc_setProperty的源码实现
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

-【3】进入reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);//设置isa指向
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);//retain新值
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;//设置新值
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;//设置新值
        slotlock.unlock();
    }

    objc_release(oldValue);//relrase 旧值
}

总结

  • 实际上objc_setProperty就是用来将上层的属性set方法与底层reallySetProperty关联。
  • 设计objc_setProperty的原因是为了避免上层set方法直接调用底层set方法,这样会产生很大的临时变量
  • 因此,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口)对外提供一个接口供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

cls 与 类 的关联原理

在此之前,需要先了解什么是联合体,为什么isa的类型isa_t是使用联合体定义

联合体(union)

构造数据类型的方式有以下两种:

  • 结构体(struct)

    结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。

    • 缺点:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费

    • 优点:存储容量较大包容性强,且成员之间不会相互影响

  • 联合体(union,也称为共用体)

    联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉

    • 缺点:包容性弱

    • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

isa的类型 isa_t

isa_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能

  • 源码实现
union isa_t { //联合体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits ,两者是互斥关系
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
  • 提供了两个成员,clsbits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式

    • 通过cls初始化,bits无默认值

    • 通过bits初始化,cls有默认值

  • 还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个定义,有两个版本 __arm64__(对应ios 移动端)__x86_64__(对应macOS),以下是它们的一些宏定义

    • nonpointer:表示是否对 isa 指针开启指针优化
      • 0:纯isa指针
      • 1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数
    • has_assoc关联对象标志位
      • 0没有,
      • 1存在
    • has_cxx_dtor:该对象是否有C++或者Objc析构器,
      • 如果有析构函数,则需要做析构逻辑,
      • 如果没有,则可以更快的释放对象
    • shiftcls存储类指针的值。开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针。
    • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
    • weakly_referenced:对象是否被指向或者曾经指向⼀个 ARC的弱变量,没有弱引⽤的对象可以更快释放
    • deallocating:标志对象是否正在释放内存
    • has_sidetable_rcextra_rc不足以保存引用计数时,标记为true,使用sidetable来进行管理,借⽤该变量存储进位
    • extra_rc:表示该对象的引⽤计数值实际上是引⽤计数值减1,例如,如果对象的引⽤计数为10,那么extra_rc9。如果引⽤计数⼤于10,则需要使⽤到上⾯的has_sidetable_rc isa的存储情况

原理探索

  • 【1】进入initInstanceIsa的路径:alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());
    //初始化isa
    initIsa(cls, true, hasCxxDtor);
}
  • 【2】进入initIsa方法的源码实现,主要是初始化isa指针
    • 通过 cls 初始化 isa
    • 通过 bits 初始化 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  // !nonpointer 执行流程,即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进行赋值 为 0x001d800000000001ULL
        // 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;
    }
}

isa 与 类 的关联

clsisa关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来

相关文章

  • iOS - Isa结构分析

    本文的主要目的是理解类与isa是如何关联的 首先了解一下OC对象的本质是什么 Clang clang是一个由App...

  • isa结构分析

    在我们iOS开发进行lldb调试的时候,经常会在控制台看到isa的存在,那么本文就来分析一下isa的结构。 在分析...

  • iOS底层之类的结构分析

    从iOS底层之isa结构分析及关联类我们探究了类的实例对象的内存结构,对象指针的首地址存储了isa,也就是存储了类...

  • iOS 类的结构分析(下)

    在上一篇 iOS 类的结构分析(上) 分析了类的结构、isa 的走位以及类的内存分布(属性列表&实例方法列表),这...

  • iOS底层-isa结构分析

    本位的主要目的是分析isa是什么还有isa的结构。 那么,在介绍isa之前,我们首先必须了解几个概念 LVVM和C...

  • iOS开发 isa结构分析

    1.什么是isa isa是一个指针,每个类对象中都有,它指向的是类对象或者该类的内存地址。类信息就存储在当前的is...

  • iOS底层isa结构分析

    在介绍正文之前,首先需要理解一个概念:OC对象的本质是什么? OC对象本质 在探索oc对象本质前,先了解一个编译器...

  • Cache_t的结构和原理

    在之间的文章里我们分析了isa的指向和结构isa结构分析,分析了bits类的结构分析,在这篇文章里,我们来分析ob...

  • iOS底层之isa走位探索

    前置文章: iOS底层之类的重要组成部分-isa结构体分析 前言 从上篇文章中我们得知对象的isa指针中的shif...

  • iOS底层探索004-类分析

    iOS底层探索-目录 1. 类的分析 主要分析两个部分:isa的走向和继承关系 isa分析 类的isa走向,参考这...

网友评论

      本文标题:iOS - Isa结构分析

      本文链接:https://www.haomeiwen.com/subject/dcsnmktx.html