- struct objc_classobjc_class与objc_object
- isa到底是什么
- ISA来了
- if arm64
- 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 \
- 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 \
- define RC_ONE (1ULL<<45)
- define RC_HALF (1ULL<<18)
- endif
- elif x86_64
- define ISA_MASK 0x00007ffffffffff8ULL
- define ISA_MAGIC_MASK 0x001f800000000001ULL
- define ISA_MAGIC_VALUE 0x001d800000000001ULL
- define ISA_HAS_CXX_DTOR_BIT 1
- define ISA_BITFIELD \
- define RC_ONE (1ULL<<56)
- define RC_HALF (1ULL<<7)
- else
- error unknown architecture for packed isa
- endif
- SuperClass
- 方法调用流程
- 最后我们在来看isa和superclass的大神图,就全都一目了然了
- ">

struct objc_classobjc_class与objc_object
在学习isa之前我么先研究一下objc_class与objc_object之间的关系。
objc_object
我们先看objc_object结构体
struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY;};
typedef struct objc_class *Class;
我们看到objc_object结构体中只有一个Class类型的isa,我们在看Class的定义是一个objc_class的结构体指针。
objc_class
我们在来看objc_class的结构体
struct objc_class {Class _Nonnull isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class _Nullable super_class OBJC2_UNAVAILABLE;const char * _Nonnull name OBJC2_UNAVAILABLE;long version OBJC2_UNAVAILABLE;long info OBJC2_UNAVAILABLE;long instance_size OBJC2_UNAVAILABLE;struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;
这里我们需要注意这个结构体已经被标注OBJC2_UNAVAILABLE目前是一个已经废弃了的结构体。
其实在当前的objc项目中在用的objc_class的结构体是我们下边的这个结构体
struct objc_class : objc_object {objc_class(const objc_class&) = delete;objc_class(objc_class&&) = delete;void operator=(const objc_class&) = delete;void operator=(objc_class&&) = delete;// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits;}
在C++中结构体是可以继承的,我们可以看到objc_class继承自objc_object结构体。
由于objc_object中只有一个isa指针,那么objc_class的结构体简化后如下:
struct objc_class : objc_object {// Class ISA;Class _Nonnull isa OBJC_ISA_AVAILABILITY;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits;}
Class _Nonnull isa:当前类对象的isa指针
Class superclass:父类
cache_t cache:类的缓存信息(后续会讲到)
class_data_bits_t bits:类的重要信息都存储在里面class_data_rw_t和class_data_ro_t中
class_data_rw_t:主要存储类的属性、实例方法、协议信息
class_data_ro_t:主要存储成员变量信息
综述:对象会被定义为objc_object结构体,类会被定义为objc_class对象,而objc_class继承自objc_objec所以类也是一个特殊的对象。
isa到底是什么
我们知道OC对象的本质其实就是结构体,OC对象对应的结构体就是struct objc_object,我们可以通过clang来讲我们的OC代码转换成C++代码,来求证一下。
Clang
- Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
- Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
- 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。
- 使用XCode自带 clang 可以将OC代码编译成C++代码
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m(源文件路径) -o main-arm64.cpp(目标文件路径)
- Xcrun: Xcode Run
- -sdk iphoneos: iOS系统
- clang:XCode自带编译环境
- -arch arm64:编译对应架构的成果物
- -rewrite-objc:将文件便以为目标C++文件
- -fobjc-arc:内存管理方式
- -fobjc-runtime=ios-8.0.0:runtime版本 当需要用到runtime时需要使用该参数
Clang转换
```objectivec @interface YJCar : NSObject
@property (copy, nonatomic) NSString *name;
- (void) sayNB;
@end
@implementation YJCar
- (void) sayNB {
}
@end
转换为C++的代码如下:```objectivec#ifndef _REWRITER_typedef_YJCar#define _REWRITER_typedef_YJCartypedef struct objc_object YJCar;typedef struct {} _objc_exc_YJCar;#endifextern "C" unsigned long OBJC_IVAR_$_YJCar$_name;struct YJCar_IMPL {struct NSObject_IMPL NSObject_IVARS;NSString *__strong _name;};// @property (copy, nonatomic) NSString *name;// - (void) sayNB;/* @end */// @implementation YJCarstatic void _I_YJCar_sayNB(YJCar * self, SEL _cmd) {}static NSString * _I_YJCar_name(YJCar * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_YJCar$_name)); }extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);static void _I_YJCar_setName_(YJCar * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct YJCar, _name), (id)name, 0, 1); }
我们可以看到YJCar被转换为了typedef struct objc_object YJCar,objc_object结构体。
我们在往下看struct NSObject_IMPL NSObject_IVARS,其实是isa指针
struct NSObject_IMPL {__unsafe_unretained Class isa;};
联合体和位域
联合体与结构体
结构体(struct)
结构体: 把不同的数据整合成一个整体, 其变量是共存的, 变量是否使用都会为其开辟内存空间。
- 优点: 储存容量大, 包容性强, 并且成员与成员之间不会互相影响
缺点: 因为结构体里面成员都会给分配内存(不管用没用到), 没有用到的会造成内存浪费
联合体(union)
联合体: 也叫共用体, 把不同的数据整合成一个整体,其变量是互斥的, 所有成员同一片内存。联合体采用内存覆盖技术, 同一时刻只能保存一个成员值, 即:
优点: 所有成员共用一段内存节省内存空间
缺点: 包容性弱, 成员互斥, 同一时刻只能保存一个成员的值
两者的区别
内存占用
- 结构体: 各个成员占用不同内存, 相互没有影响
- 联合体: 各个内存占用同一内存, 修改一个成员影响其他所有成员
内存分配
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 在arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
- 需要将isa的内容&ISA_MASK位运算后得到具体的Class、Meta-Class的内存地址
- 共用体类似结构体,共用体内的元素公用同一块内存空间
- 共用体的内存大小至少为公用体内最大元素的内存空间大小
- 共用体内可通过位域的方式为不同的元素分配制定的内存空间大小
- 共用体内内存的排放为从后向前排
- isa共用体结构

- nonpointer:
- 0:代表普通的指针,存储着Class、Meta-Class对象的内存地址
- 1:代表优化过,使用位域存储更多的信息
- has_assoc:
- 是否有设置过关联对象,如果没有,释放时会更快
- has_cxx_dtor:
- nonpointer:
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
- shiftcls:
- 存储着Class、Meta-Class对象的内存地址信息
- magic:
- 用于在调试时分辨对象是否未完成初始化
- weakly_referenced:
- 是否有被指向或者曾经指向过ARC的若变量,如果没有,释放时会更快
- deallocating:
- 对象是否正在释放内存
- extra_rc:
- 里边存储的值时引用计数器减1
- has_sidetable_rc:
- 引用计数器是否过大无法存储在isa中
- 如果为1,那么引用计数器会存储在一个叫SideTable的类的属性中
Shiftcls在ARM和X86平台是有一定区别的
```objectivecif 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 : 8define 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 : 19define RC_ONE (1ULL<<45)
define RC_HALF (1ULL<<18)
endif
elif x86_64
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
我们可以看到在ARM64平台占用的是3~36字节,共占33字节<br /> uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/<br />而在X86也就是我们的模拟上占用的是3~47字节,共占44字节<br /> uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/<a name="NHNfj"></a>### 通过Shitcls获取Mate元类我们以X86平台为例<a name="fMIVk"></a>#### 方式一:位运算移动其实我们要想获取Shitcls的信息,只需要将isa_t中的值向右移动3字节,在向左移动20字节,在向右移动17字节即可<br />我们可以通过位运算左移右移来处理<br />示例:<br /><br />我们在继续查看一下元类的isa是什么<br /><br />方式二:与运算<br /><br />通过上述lldb的方式分析来看,我们已经知道了isa的结构体,并且也已经了解到了,实例对象的isa指向关系。<br />总结:<br />实例对象isa-->类对象isa-->元类isa-->根原类isa-->根原类isa-->根原类isa<a name="hqK7X"></a># isa和实例对象关系的建立在上一节我们分析alloc的过程,在createInstanceFromZone函数中,通过initIsa函数将我们创建的objc对象与类进行关联。<br />我们先来看一下initIsa的结构体代码```objectivecobjc_object::initIsa(Class cls){initIsa(cls, false, false);}
SuperClass
superclass存储的就是当前类的的父类信息,实例对象没有supperclass,只有类对象才有superclass。这也就是objc_object结构体只有isa的原因。
总结:
Class对象SupperClass—>父Class—>父Class—>……—>NSobject—>nil
方法调用流程
方法调用分为实例方法调用个类方法调用,在研究调用之前我们先了解一下这心方法的存储位置。
instance对象(实例对象)

- instance对象的isa指向class
- 当调用对象方法时,先通过isa找到class对象,然后在找到对象方法进行调用
- class的isa指向元类的class,也就是mate_class
当调用类方法时,需要通过isa找到class,在通过class的isa找到元类,然后在找到类方法进行调用
class对象(类对象)

当Student的instance对象要调用Person对象的实例方法时,会先通过instance的isa找到Student的class,然后在Student的class的superclass找到Person,最后找到方法进行调用。
mate_class对象(元类对象)

当Student的class对象要调用Person的对象方法时,会先通过Student的isa找到Student的mate_class,在通过Student的mate_class的superclass找到Person的mate_class,最后找到Person的类方法进行调用
- 此处特殊:当通过superclass查找父类的方法时,一直找到NSObject的时候都没找到方法,系统会想前查找一次,也就是会找到根元类的类对象的实例方法,如果找到了也会进行调用,在找不到就会报错。
最后我们在来看isa和superclass的大神图,就全都一目了然了
万能图

KC老师拆分图


