美文网首页
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