美文网首页
iOS对象的本质

iOS对象的本质

作者: 镜像 | 来源:发表于2021-06-20 14:24 被阅读0次

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

C++文件

我们通过clang命令,可以把我们OC中.m文件转化成.cpp文件,里面可以看到OC底层的C++实现,不过这个也不是100%准确,但是给我们的学习可以提供很好的参考。
我们首先创建一个对象SJPerson,添加一个属性sjName

image
命令行进入到main.m目录下,执行clang -rewrite-objc main.m -o main.cpp,我们就可以生成对应的C++文件,拖到工程中查看了。

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

对象的本质

全局搜索SJPerson,找到最关键信息

/// 对象底层就是结构体指针
struct SJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_sjName;
};

再找一下NSObject_IMPL

struct NSObject_IMPL {
    Class isa;
};

这下我们就可以看到SJPerson在底层是个结构体,结构体有isa指针和我们写的属性对应的成员变量_sjName。因为iOS中大部分类都继承自NSObject,所以这些类都有isa指针。
再看两个细节:

typedef struct objc_object SJPerson;

为什么SJPerson的类型是objc_object,因为NSObject下层类型就是objc_object

typedef struct objc_class *Class;

Class下层的类型就是objc_class。Class就是一个结构体指针。

typedef struct objc_object *id;

我们看到了id类型,iOS中id可以指向任何类型,并且不用加*号的原因就在这里了~
接下来我们再看下sjName的get和set方法

/// sjName的get方法
static NSString * _I_SJPerson_sjName(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_sjName)); }

/// sjName的set方法
static void _I_SJPerson_setSjName_(SJPerson * self, SEL _cmd, NSString *sjName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _sjName), (id)sjName, 0, 1); }

我们看到这两个方法里面都有两个参数self_cmd,但是我们在OC中并没有看到这两个参数,这两个就是隐藏参数,OC中每个方法都有这两个参数。
为什么使用self + OBJC_IVAR_$_SJPerson$_sjName能拿到当前的成员变量呢,因为self就是当前结构体首地址,OBJC_IVAR_$_SJPerson$_sjNamesjName在结构体中相对于首地址的偏移量,所以这样写可以拿到对应的成员变量。

isa探索

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

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

在源码中我们看到isa指针使用的共用体和位域技术。里面有两个成员bitscls,使用共用体可以大大节约内存空间。
isa里面是怎么存放数据的呢,根据经验,我们找到里面的结构体,不出意外结构体应该是这个共用体每位数据的说明

struct {
        ISA_BITFIELD;  // defined in isa.h
    };

ISA_BITFIELD是个宏定义,再看下:

/// 真机
# if __arm64__
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19

/// 模拟器
# elif __x86_64__
#   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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
# endif

每项数据说明:

  • nonpointer:指针是否进行优化,0:未优化,是个单纯的指针;1:优化的指针,指针里面存储着类相关的信息
  • has_assoc:是否有关联对象,如果没有,释放更快
  • has_cxx_dtor:是否有C++析构函数,如果没有,释放更快
  • shiftcls:类(元类)地址
  • magic:调试时分辨对象是否未完成初始化
  • weakly_referenced:是否被弱引用指向,如果没有,释放更快
  • unused:是否正在被释放
  • extra_rc:引用计数减1
  • has_sidetable_rc:引用计数过大无法存在isa中,值为1时,引用计数会存在SideTable

isa的赋值过程

我们知道对象初始化最后会走到initIsa方法来初始化,其中的nonpointer参数的值为true,证明初始化这个isa是优化过的。

image

isa经过newisa(0)初始化后,每一位数据都为0。

image
然后进行赋值
image
赋值后的数据:
image

我们再看下一setClass方法

image
shiftcls赋值的时候往右移了3位,因为上面说到isa存储类信息是从第4位开始的。

总结:对象的本质就是含有isa指针的结构体,arm64后,isa利用位域技术存储了很多关于对象的信息。

相关文章

网友评论

      本文标题:iOS对象的本质

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