iOS底层探索对象的本质&NONPOINTER_ISA

1.编译器Clang的使用

Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。ios

使用Clangmain.m文件编译成C++文件c++

clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件markdown

遇到UIKit报错问题:架构

image.png

解决clang编译遇到UIKit报错的两种方式:app

  • 第一种是配置sdk版本及路径

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.3.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk main.miphone

路径中的系统版本按照xCode中SDK版本修改(当前为模拟器SDK)ide

image.png

  • 第二种是使用xcrun命令

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp函数

image.png

2.对象的本质

使用clang -rewrite-objc main.m -o main.cpp命令编译main.m文件,main.m文件内容以下:post

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

@interface ABPerson : NSObject
@property (nonatomic,strong) NSString *name;
@end

@implementation ABPerson
@end

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

main.cpp文件中的ABPerson以下:优化

struct ABPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
复制代码
struct NSObject_IMPL {
	Class isa;
};
复制代码
//Class就是结构体指针
typedef struct objc_class *Class;
复制代码

因此,对象在底层的本质就是结构体

3.对象属性底层分析

//getter方法
static NSString * _I_ABPerson_name(ABPerson * self, SEL _cmd)
{
    return (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name));
}
//setter方法
static void _I_ABPerson_setName_(ABPerson * self, SEL _cmd, NSString *name)
{
    (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)) = name;
}
复制代码

成员变量的内存访问是经过先拿到对象的首地址加上变量的偏移地址,再根据计算的地址访问内存

  • (char *)self:对象的首地址
  • OBJC_IVAR_$_ABPerson$_name:成员变量name的偏移地址
  • *(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)成员变量name的内存

4. 位域

struct StructCar1 {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};
struct StructCar2 {
    BOOL front: 1;
    BOOL back: 1;
    BOOL left: 1;
    BOOL right: 1;
};
复制代码
  • 结构体StructCar14个字节,由于其内部成员变量都是BOOL类型,其实用1个字节就能放下
  • 结构体StructCar21个字节,节省了3个字节。

Untitled Diagram.png

5. 共用体(联合体)

image.png

  • 结构体共存:name和age都有值

image.png

  • 共用体互斥:name和age只能有一个有值,最后一个被赋值,前面的被清空。

结构体(struct)中全部变量是“共存”的,优势是“有容乃大”,全面;缺点是结构体内存空间分配是粗放的,无论用不用全分配。

共用体(union)中是各变量是“互斥”的,缺点是不够“包容”;优势是内存使用更为精细灵活,也节省了内存空间

6.NONPOINTER_ISA

ISA分为纯的ISANONPOINTER_ISANONPOINTER_ISA除了包含一个纯指针外,还包含了类的一些信息。

探索alloc流程一文中,最终会经过initIsa将从堆申请的结构体指针和当前的class绑定在一块儿。

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}
复制代码
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);
    //省略部分代码
 }
复制代码
//共用体
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_BITFIELDarm64x86_64架构下的定义

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
//模拟器
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
//真机
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
//macOS
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif
复制代码
  • nonpointer:表示是否对 isa 指针开启指针优化,0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等
  • has_assoc:关联对象标志位,0没有,1存在
  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,若是有析构函数,则须要作析构逻辑, 若是没有,则能够更快的释放对象
  • shiftcls:存储类指针的值。开启指针优化的状况下,在 arm64 架构中有 33 位用来存储类指针

-magic:用于调试器判断当前对象是真的对象仍是没有初始化的空间

  • weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,

没有弱引用的对象能够更快释放

  • unused:是否未被使用
  • has_sidetable_rc:散列表,当对象引用技术大于 10 时,则须要借用该变量存储进位
  • extra_rc:当表示该对象的引用计数值,其实是引用计数值减 1, 例如,若是对象的引用计数为 10,那么 extra_rc 为 9。若是引用计数大于 10, 则须要使用到下面的 has_sidetable_rc

ISA_BITFIELD各元素存储分布图

a.png

7.ISA的位运算

经过ISA的位运算拿到shiftcls。以macOS为例,shiftcls64位结构中的位置:右边有3位,左边有17位。本身包含44位。

aa.png

代码lldb验证

image.png 俩红框值是相等的,说明位运算成功拿到了shiftcls

8.总结

  • 对象在底层的本质就是结构体
  • ISA是经过共用体(联合体)互斥的特性,肯定ISA是纯的ISA仍是NONPOINTER_ISA,并经过位域来实现节约空间。