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 vtable
class_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 vtable
class_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转换
@interface YJCar : NSObject
@property (copy, nonatomic) NSString *name;
- (void) sayNB;
@end
@implementation YJCar
- (void) sayNB
{
}
@end
转换为C++的代码如下:
#ifndef _REWRITER_typedef_YJCar
#define _REWRITER_typedef_YJCar
typedef struct objc_object YJCar;
typedef struct {} _objc_exc_YJCar;
#endif
extern "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 YJCar
static 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;
};
联合体和位域
在探索ISA之前我们需要先了解一下联合体与联合体位域知识
联合体与结构体
联合体与结构体都是基础的构造数据类型
结构体(struct)
结构体: 把不同的数据整合成一个整体, 其变量是共存的, 变量是否使用都会为其开辟内存空间。
- 优点: 储存容量大, 包容性强, 并且成员与成员之间不会互相影响
- 缺点: 因为结构体里面成员都会给分配内存(不管用没用到), 没有用到的会造成内存浪费
联合体(union)
联合体: 也叫共用体, 把不同的数据整合成一个整体,其变量是互斥的, 所有成员同一片内存。联合体采用内存覆盖技术, 同一时刻只能保存一个成员值, 即:
- 优点: 所有成员共用一段内存节省内存空间
- 缺点: 包容性弱, 成员互斥, 同一时刻只能保存一个成员的值
两者的区别
- 内存占用
-
- 结构体: 各个成员占用不同内存, 相互没有影响
- 联合体: 各个内存占用同一内存, 修改一个成员影响其他所有成员
- 内存分配
-
- 结构体: 开辟的内存大于等于所有成员总和(对齐可能导致有空余)
- 联合体: 内存占用为最大成员占用的内存
ISA来了
- 在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:
-
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
- shiftcls:
-
- 存储着Class、Meta-Class对象的内存地址信息
- magic:
-
- 用于在调试时分辨对象是否未完成初始化
- weakly_referenced:
-
- 是否有被指向或者曾经指向过ARC的若变量,如果没有,释放时会更快
- deallocating:
-
- 对象是否正在释放内存
- extra_rc:
-
- 里边存储的值时引用计数器减1
- has_sidetable_rc:
-
- 引用计数器是否过大无法存储在isa中
- 如果为1,那么引用计数器会存储在一个叫SideTable的类的属性中
Shiftcls在ARM和X86平台是有一定区别的
# 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__
# 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字节
uintptr_t shiftcls : 33; /MACH_VM_MAX_ADDRESS 0x1000000000/
而在X86也就是我们的模拟上占用的是3~47字节,共占44字节
uintptr_t shiftcls : 44; /MACH_VM_MAX_ADDRESS 0x7fffffe00000/
通过Shitcls获取Mate元类
我们以X86平台为例
方式一:位运算移动
其实我们要想获取Shitcls的信息,只需要将isa_t中的值向右移动3字节,在向左移动20字节,在向右移动17字节即可
我们可以通过位运算左移右移来处理
示例:
我们在继续查看一下元类的isa是什么
方式二:与运算
通过上述lldb的方式分析来看,我们已经知道了isa的结构体,并且也已经了解到了,实例对象的isa指向关系。
总结:
实例对象isa-->类对象isa-->元类isa-->根原类isa-->根原类isa-->根原类isa
isa和实例对象关系的建立
在上一节我们分析alloc的过程,在createInstanceFromZone函数中,通过initIsa函数将我们创建的objc对象与类进行关联。
我们先来看一下initIsa的结构体代码
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
我们发现结构内部调用了initIsa()方法
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老师拆分图
\