iOS底层原理(3)---OC对象原理下

323 阅读4分钟

前面已经探索了 alloc对象初始化内存对齐,接下来我们继续来学习 对象的本质

一、clangxcrun

clang

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

因为OC是C、C++的超集,通过clang可以将m文件编译成cpp文件。

用法:

clang -rewrite-objc xx.m -o xx.cpp 

xcrun

xcrun是xcode安装时候带的一个命令工具,是在clang基础上进行封装得来。

用法:

1、模拟器

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc xx.m -o xx.cpp 

2、⼿机

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xx.m -o xx.cpp

二、对象本质及扩展

首先我们通过clang将下面的main.m文件编译为cpp文件:

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

// 对象在底层的本质就是结构体
@interface LGPerson : NSObject

@end

@implementation LGPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}

执行命令:clang -rewrite-objc main.m -o main.cpp 由于.cpp文件中有很多头文件的展开,与我们本次的探索目标无关,所以我摘录以下关键的代码:

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

//这是LGPerson对象
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;//结构体的继承,成员变量ISA
};

struct NSObject_IMPL {
	Class isa;
};

//Class 是一个结构体指针,只是一个 objc_class * 的别名而已
typedef struct objc_class *Class;


struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

//id 是 objc_object * 的别名
typedef struct objc_object *id;



typedef struct objc_selector *SEL;


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_vj_4mx0r3r94cb6lxl6pxdh225r0000gn_T_main_f586d8_mi_0);
    }
    return 0;
}

struct _prop_t {
	const char *name;
	const char *attributes;
};

struct _protocol_t;

struct _objc_method {
	struct objc_selector * _cmd;
	const char *method_type;
	void  *_imp;
};

struct _protocol_t {
	void * isa;  // NULL
	const char *protocol_name;
	const struct _protocol_list_t * protocol_list; // super protocols
	const struct method_list_t *instance_methods;
	const struct method_list_t *class_methods;
	const struct method_list_t *optionalInstanceMethods;
	const struct method_list_t *optionalClassMethods;
	const struct _prop_list_t * properties;
	const unsigned int size;  // sizeof(struct _protocol_t)
	const unsigned int flags;  // = 0
	const char ** extendedMethodTypes;
};

struct _ivar_t {
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};

struct _class_ro_t {
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	unsigned int reserved;
	const unsigned char *ivarLayout;
	const char *name;
	const struct _method_list_t *baseMethods;
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;
};

struct _class_t {
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

三、联合体位域拓展补充

位域:

struct LGCar1 {
    BOOL front; // 0 1
    BOOL back;
    BOOL left;
    BOOL right;
};

// 位域,节约内存
// 互斥
struct LGCar2 {
    BOOL front: 1;
    BOOL back : 2;
    BOOL left : 6;
    BOOL right: 1;
};

struct LGCar1 car1;
struct LGCar2 car2;
NSLog(@"%ld-%ld",sizeof(car1),sizeof(car2));//打印 4  1

联合体(又叫共用体):

// 共存
struct LGTeacher1 {
    char        *name;
    int         age;
    double      height ;
};

// 联合体 : 互斥 
union LGTeacher2 {
    char        *name;
    int         age;
    double      height ;
};

struct LGTeacher1   teacher1;
teacher1.name = "Cooci";
teacher1.age  = 18;

union LGTeacher2    teacher2;
teacher2.name = "Cooci";
teacher2.age  = 18;

下断点一步步调试打印:

(lldb) p teacher1
(LGTeacher1) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher1
(LGTeacher1) $1 = (name = "Cooci", age = 0, height = 0)
(lldb) p teacher1
(LGTeacher1) $2 = (name = "Cooci", age = 18, height = 0)

(lldb) p teacher2
(LGTeacher2) $5 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher2
(LGTeacher2) $6 = (name = "Cooci", age = 15964, height = 2.1220036782292425E-314)
(lldb) p teacher2
(LGTeacher2) $7 = (name = "", age = 18, height = 2.1219957998584539E-314)

总结:结构体中所有变量是“共存”的;联合体中所有变量是“互斥”的,同一时间只能有一个变量被赋值;

四、nonPointerIsa的分析

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

#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;  //关联对象                                       \
      uintptr_t has_cxx_dtor      : 1;  //c++的析构函数                                       \
      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//引用计数

解析:

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_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位

extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1, 例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10, 则需要使⽤到下⾯的 has_sidetable_rc

五、isa 推导 class

image.png

(lldb) x/4gx p
0x10054fc40: 0x011d800100008275 0x0000000000000000 //第一块是isa
0x10054fc50: 0x0000000000000000 0x0000000000000000
(lldb) p/t 0x011d800100008275
(long) $1 = 0b0000000100011101100000000000000100000000000000001000001001110101
(lldb) p/x LGPerson.class //类
(Class) $2 = 0x0000000100008270 LGPerson 
(lldb) p/x 0x011d800100008275 & 0x00007ffffffffff8ULL //掩码 #   define ISA_MASK        0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008270  //与类一样的地址

以上便是通过 isa 掩码 找到 cls

isa的位运算

结合 ISA_BITFIELD 的定义,通过 isa 的位运算找到 cls

(lldb) p/x 0x011d800100008275 >> 3
(long) $9 = 0x0023b0002000104e
(lldb) p/x 0x0023b0002000104e << 20
(long) $10 = 0x0002000104e00000
(lldb) p/x 0x0002000104e00000 >> 17
(long) $11 = 0x0000000100008270
(lldb) p/x LGPerson.class
(Class) $12 = 0x0000000100008270 LGPerson