iOS底层-7:isa与类的关联

对象的本质

OC对象在底层到底是以什么样的形式存在的呢?我们借助Clang把代码编译成c++代码,更利于我们学习。

Clang

Clang是一个C、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。

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

2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下一个小更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。

Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC

  • Clang简单命令
    • clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件

    • 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 (手机)
Clang的使用
  • 新建一个Mac工程,在main.m中新建一个简单的LRPerson类,选中main.m,然后Show in Finder

    image.png

  • 打开终端,cd进入当前目录

  • 终端输入clang -rewrite-objc main.m -o main.cpp回车

  • Finder查看当前目录,发现多了一个main.cpp文件

    image.png

  • 双击打开main.cpp文件,搜索LRPerson,找到如图位置
    image.png

这个就是LRPerson类编译后的代码

如若不信的话,我们给LRPerson类加一个name属性,再重新生成一次。

image.png

看这两个对比图,我们发现结构体struct里面确实多了一个成员变量_name,下面还多了settergetter方法。由此可见,对象在底层会编译成结构体。上面的struct NSObject_IMPL NSObject_IVARS;其实就是isa

拓展 objc_setProperty

通过clang编译后,所有的setter方法到底层都是通过objc_setProperty实现的。我们来看一下objc_setProperty的源码

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);
        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);
    }

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

    objc_release(oldValue);
}

这段代码主要就是对新值retain,对旧值release

联合体 位域

写一个简单的联合体(union)

@interface LRPerson : NSObject
{
    //联合体
    union {
        char bits;
        //位域
        struct {            //0000 0000 bits总共一个字节 8位
            char front : 1; //0000 000X中,X位置存储front数据,数字1 表示front 占用1位 
            char back  : 1; //0000 00X0中,X位置存储back数据
            char left  : 1;
            char right : 1;
        };
    } _direction;
}

@end
  • 联合体简单赋值与取值
#define LRDirectionFrontMask (1 << 0)
#define LRDirectionBackMask  (1 << 1)
#define LRDirectionLeftMask  (1 << 2)
#define LRDirectionRightMask (1 << 3)

- (instancetype)init {
    if (self = [super init]) {
        _direction.bits = 0b00000000;
    }
    return self;
}
//0000 0000
//0000 0001
//或运算
//0000 0001
//
//0000 0000
//1111 1110
//与运算
//0000 0000

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _direction.bits |= LRDirectionFrontMask;
    }else{
        _direction.bits &= ~LRDirectionFrontMask;
    }
    NSLog(@"%s",__func__);
}

- (BOOL)isFront {
    return _direction.front;
}
- (void)setRight:(BOOL)isRight{
    _direction.right = isRight;
    NSLog(@"%s",__func__);
}

- (BOOL)isRight {
    return _direction.right;
}

setter方法可以直接设置_direction.right,也可以设置 _direction.bits

结构体(struct)中所有变量是“共存”的,优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间

Isa的结构信息

在之前的源码探索:alloc & init & new 源码分析有涉及到initIsa函数,我们在这里详细分析一下isa的结构。

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

        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;
    }
}

isa的初始化分为两种情况

  • nonpointertrue时,用else里面的办法初始化,先new一个isa_t,接着设置它的bitshas_cxx_dtorshiftcls等参数
  • nonpointerfalse时,直接用上面isa_t((uintptr_t)cls);函数初始化

接着我们看一下isa_t的源码

isa_t

宏定义ISA_BITFIELD只截取了我们主要分析的__arm64__的环境。


# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

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
};

我们可以看到isa_t就是一个联合体(union),有两个isa_t()的初始化函数,还有clsbits两个成员变量,他们是互斥的。isa_t初始化的时候会初始化bits或者是cls。一般我们自己定义的类都是nonpointer isa,初始化bits

  • bits下面的宏定义就是位域,即是各个变量所占的位置和大小。

各个变量所表示的含义为:

  • nonpointer 表示是否对isa指针开启指针优化,0:纯isa指针;1:不只是类对象地址,isa包含了类信息、对象的引用计数等 (0号位置)

  • has_assoc 关联对象标志位,0 没有;1 存在 (1号位置)

  • has_cxx_dtor 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 (2号位置)

  • shiftcls 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。(3-35号位置)

  • magic 用于调试器判断当前对象是真的对象还是没有初始化的空间 (36-41号位置)

  • weakly_referenced 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。(42号位置)

  • deallocating 标志对象是否正在释放内存 (43号位置)

  • has_sidetable_rc 当对象引用计数大于 10 时,则需要借用该变量存储进位 (44号位置)

  • extra_rc 表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到has_sidetable_rc。 (45-63号位置)

这里拓展一下dealloc源码,看看对象在释放的时候对ias做了什么。

dealloc

搜索dealloc {

image.png

通过_objc_rootDealloc跳转到objc_object::rootDealloc函数

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    //是TaggedPointer直接返回
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&                  //nonpointer isa 
                 !isa.weakly_referenced  &&          //没有弱引用
                 !isa.has_assoc  &&                  //没有关联对象
                 !isa.has_cxx_dtor  &&               //没有c++析构函数
                 !isa.has_sidetable_rc))             //没有用到变量存储进位,引用计数不大于10
    {
        assert(!sidetable_present());  
        free(this);                                  //满足上述条件的,直接释放
    } 
    else {
        object_dispose((id)this);                    //else 调用object_dispose函数
    }
}
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);                      //调用objc_destructInstance函数
    free(obj);                                       //释放对象

    return nil;
}
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);             //有c++析构方法的,先调用析构方法
        if (assoc) _object_remove_assocations(obj);   //有关联对象的,移除关联对象
        obj->clearDeallocating();
    }

    return obj;
}

对象是否能被释放的信息,都被存储在这些标志位里面。在对象被释放的时候只要获取ias里面的信息即可知道该对象能不能被释放。

isa关联类信息

我们在objc_object::initIsa函数中通过断点分析,isa的初始化过程。

  • initIsa函数中打上断点,等LRPerson类(自定义类)进来之后,我们开始分析。

    image.png

  • 查看另外的两个参数nonpointerhasCxxDtor,皆为true

    image.png

image.png
  • nonpointertrue,会调用else里面的初始化方法。

    image.png

  • 断点在2号位置时,打印查看newisa

    image.png

    刚刚初始化的newisa,里面标志位都为0。

  • 断点在3号位置时,打印查看newisa

image.png

newisabitsISA_MAGIC_VALUE赋值后的数据,探索用的工程是Mac工程,附上相应的宏定义

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

ISA_MAGIC_VALUE的值是0x001d800000000001ULL,控制台打印p 0x001d800000000001ULL输出结果正是上图中的bits

image.png

再打印出2进制形式结果为0b0000000000011101100000000000000000000000000000000000000000000001

image.png

最后一位的1,正是nonpointer,与前面结果相符合

magic对应的6位是111011,二进制换算成10进制,即为59,与结果符合

  • 断点在4号位置时
    image.png

has_cxx_dtor赋值为true后,上面的二进制0b0000000000011101100000000000000000000000000000000000000000000001变为0b0000000000011101100000000000000000000000000000000000000000000101倒数第三位改变
打印成整型为8303511812964357,与上图bits一致。

image.png

  • 断点在5号位置时,shiftcls被赋值
    • 首先二进制打印clsLRPerson结果为0b0000000000000000000000000000000100000000000000000010010011010000

      image.png

    • 右移3位0b0000000000000000000000000000000000100000000000000000010010011010

      image.png

      shiftcls赋值之后,newisa的值为0b0000000000011101100000000000000100000000000000000010010011010101(实际就是将$23后44位赋值给newisa18到61位)

    • 将结果打印为整型为8303516107941077

    • 打印查看newisa,同上面计算一致;cls也被赋值为了LRPerson,对象的isa与类关联起来了

      image.png

你可能感兴趣的:(iOS底层-7:isa与类的关联)