Date | Notes | Refer. |
---|---|---|
2019-03-18 | 首次提交 | objc4-750 |
2019-08-18 | 抽离 isa 部分;重整文章结构 |
Obj-C 中的 isa 指针 - kingcos |
2019-08-29 | 增加 class_rw_t 等细节 |
- |
2019-12-23 | 细节微调 | objc4-756.2 |
Preface
Obj-C 中的对象分为实例对象(Instance Object)、类对象(Class Object)、以及元类对象(Meta-class Object)三种,本文将借助源码简单了解一下不同类型对象的真实构造。
为了方便下文举例,这里我们定义一个 EDdeviceProtocol
协议,并让继承自 NSObject
的 Computer
类遵守该协议:
// EDdeviceProtocol 协议
@protocol EDdeviceProtocol
- (void)powerOn;
@end
// Computer 类,继承自 NSObject,遵守 EDdeviceProtocol
@interface Computer : NSObject <EDdeviceProtocol> {
// 成员变量
@public
int _memorySize;
int _diskSize;
}
// 属性
@property (copy) NSString *model;
// 实例方法
- (void)print:(NSString *)content;
// 类方法
+ (NSString *)arch;
@end
@implementation Computer
- (void)print:(NSString *)content {
NSLog(@"Print content: %@.", content);
}
// 协议方法
- (void)powerOn {
NSLog(@"%s", __func__);
}
+ (NSString *)arch {
return @"ARM 64";
}
@end
实例对象
我们创建一个 Computer
类型的实例对象,并对其中的成员变量赋值:
// cpt 为指向 Computer 类型实例对象的指针
Computer *cpt = [[Computer alloc] init];
// ERROR: Direct access to Objective-C's isa is deprecated in favor of object_getClass()
// cpt->isa;
cpt->_memorySize = 16;
cpt->_diskSize = 512;
cpt.model = @"MacBook Pro 13'";
我们在程序结束前的 return 0;
一行打个断点,即可看到 cpt
指向的实例对象中存储的数据:
其中存放了 isa
指针成员变量(但无法直接访问)以及其它成员变量的具体值。isa
并不是开发者自己定义的,而是继承自 NSObject
基类。
// NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
类对象
我们知道,只要创建的实例对象属于同一个类,那么它们就都可以调用该类的实例方法,因此这些方法其实并不需要每个实例对象都单独保存一份。在 Obj-C 中,实例方法存放在类对象中,类方法存储在元类对象中。与实例对象不同,类对象和元类对象并不需要开发者手动创建。类对象可以通过 [cpt class]
、[Computer class]
或 Runtime API object_getClass
获取:
// 获取类对象的方法:
Class cptClass1 = [cpt class];
Class cptClass2 = [Computer class];
Class cptClass3 = object_getClass(cpt);
// LLDB:
// (lldb) p/x cptClass1
// (Class) $0 = 0x00000001000013d0 Computer
// (lldb) p/x cptClass2
// (Class) $1 = 0x00000001000013d0 Computer
// (lldb) p/x cptClass3
// (Class) $2 = 0x00000001000013d0 Computer
类对象的类型是 Class
,本质是指向 objc_class
结构体的指针。objc_class
结构体中存放了 isa
、superclass
、方法缓存、以及为该类实例对象所公用的一些信息:
// Object.mm
typedef struct objc_class *Class;
// objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
// 父类指针
Class superclass;
// 方法缓存,详见 Reference
cache_t cache; // formerly cache pointer and vtable
// 可读可写表(class_rw_t)等
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// ...
};
// objc-private.h
struct objc_object {
private:
// isa 指针
isa_t isa;
// ...
}
class_data_bits_t
先来看看 objc_class
中的 class_data_bits_t
:
// objc-runtime-new.h
struct objc_class : objc_object {
// objc_class 中的 data 方法,返回指向 class_rw_t 的指针
class_rw_t *data() {
// bits 即 objc_class 中 class_data_bits_t 结构体类型的成员变量
return bits.data();
}
// ...
}
#if !__LP64__
// 非 64 位,跳过
#elif 1
// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE (1UL<<1)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<2)
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#else
// 不会落到该 case 下,跳过
#endif
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...
}
// sys/_types/_uintptr_t.h
typedef unsigned long uintptr_t;
如上,class_data_bits_t
结构体中只有一个 uintptr_t bits
成员变量,其本质是 unsigned long
类型,占用 8 个字节,即 64 位。但其存储方式类似位域,将不同的数据存储在不同的位中(关于位域可详见《C/C++ 中的位域与共用体》一文):
Bits | Explanation |
---|---|
0 ~ 1 | FAST_IS_SWIFT_LEGACY :标志该类是否来自于 ABI 预稳定版本的 Swift |
1 ~ 2 | FAST_IS_SWIFT_STABLE :标志该类是否来自于 ABI 稳定版本的 Swift |
2 ~ 3 | FAST_HAS_DEFAULT_RR :类或父类含有默认的持有或引用(Retain / Reference)retain /release /autorelease /retainCount /_tryRetain /_isDeallocating /retainWeakReference /allowsWeakReference |
3 ~ 47 | FAST_DATA_MASK :指向 class_rw_t 结构体的指针 |
47 ~ 63 | 为了字节对齐,掩码填充 0 |
class_rw_t
class_rw_t
指的是可读可写(Read-Write)表。该结构体中存储了指向 class_ro_t
只读表的指针、以及方法、属性、协议等信息:
// objc-runtime-new.h
// 可读可写表
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
// 只读表的指针(const:不可修改指针指向内存空间中的数据)
const class_ro_t *ro;
// 方法、属性、协议信息
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
// ...
};
其中 method_array_t
、property_array_t
与 protocol_array_t
类型的结构几乎一致,这里以 method_array_t
为例:
// objc-runtime-new.h
// method_array_t 继承自 list_array_tt,其中 List 类型为 method_list_t,Element 类型为 method_t
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
// ...
};
// list_array_tt 中定义了元素类型为 Element,列表为 List
template <typename Element, typename List>
class list_array_tt {
// ...
};
// method_t 即是 Obj-C 中方法的结构(详见下文)
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
// ...
};
// method_list_t 继承自 entsize_list_tt,其中 List 类型为 method_list_t,Element 类型为 method_t
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
// ...
};
// entsize_list_tt 是非脆弱(Non-fragile)结构体数组的一般实现
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
// ...
};
由于 Obj-C 中的 Class
是一个不透明类型,但我们可以尝试一下将类对象的原始结构桥接到其中,首先需要将以下我整理好的内容放在 .h
引入,需要注意的是引入的实现文件需要支持 C++,即以 .mm
结尾:
-> 点击此处即可查看完整内容 <-
//
// class_details.h
// Powered by kingcos.me
//
// Created by kingcos on 2019/8/31.
// Copyright © 2019 kingcos. All rights reserved.
//
#ifndef class_details_h
#define class_details_h
#define FAST_DATA_MASK 0x00007ffffffffff8UL
typedef struct v_objc_class *V_Class;
typedef unsigned long v_uintptr_t;
typedef v_uintptr_t v_cache_key_t;
typedef uint32_t v_mask_t;
typedef v_uintptr_t v_protocol_ref_t;
struct v_bucket_t {
IMP _imp;
v_cache_key_t _key;
};
struct v_cache_t {
struct v_bucket_t *_buckets;
v_mask_t _mask;
v_mask_t _occupied;
};
union v_isa_t {
V_Class cls;
v_uintptr_t bits;
};
struct v_objc_object {
v_isa_t isa;
};
template <typename Element, typename List, uint32_t FlagMask>
struct v_entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
};
struct v_method_t {
SEL name;
const char *types;
IMP imp;
};
struct v_method_list_t : v_entsize_list_tt<v_method_t, v_method_list_t, 0x3> {
};
struct v_protocol_list_t {
uintptr_t count;
};
struct v_ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
};
struct v_ivar_list_t : v_entsize_list_tt<v_ivar_t, v_ivar_list_t, 0> {
};
struct v_property_t {
const char *name;
const char *attributes;
};
struct v_property_list_t : v_entsize_list_tt<v_property_t, v_property_list_t, 0> {
};
struct v_class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
v_method_list_t * baseMethodList;
v_protocol_list_t*** * baseProtocols;
const v_ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
v_property_list_t *baseProperties;
};
template <typename Element, typename List>
class v_list_array_tt {
union {
List* list;
v_uintptr_t arrayAndFlag;
};
};
class v_method_array_t :
public v_list_array_tt<v_method_t, v_method_list_t>
{
};
class v_property_array_t :
public v_list_array_tt<v_property_t, v_property_list_t>
{
};
class v_protocol_array_t :
public v_list_array_tt<v_protocol_ref_t, v_protocol_list_t>
{
};
struct v_class_rw_t {
uint32_t flags;
uint32_t version;
const v_class_ro_t *ro;
v_method_array_t methods;
v_property_array_t properties;
v_protocol_array_t protocols;
};
struct v_class_data_bits_t {
v_uintptr_t bits;
v_class_rw_t* data() {
return (v_class_rw_t *)(bits & FAST_DATA_MASK);
}
};
struct v_objc_class : v_objc_object {
V_Class superclass;
v_cache_t cache;
v_class_data_bits_t bits;
v_class_rw_t *data() {
return bits.data();
}
};
#endif /* class_details_h */
我们仍以 Computer
类为例,将类或元类对象转换为其真实结构时,我们就能看到其中的细节了:
// main.mm
V_Class cptClassObject = (__bridge V_Class)[Computer class];
v_class_rw_t *cptRWTable = cptClassObject->data();
有心的同学可能会发现,这里的方法列表个数显示为 5,根据上面的 Obj-C 源码,应当是
model
属性自动生成的 getter & setter,以及print:
和arch
,那么怎么会多一个呢?其实这是由于定义属性,增加了一个名为.cxx_destruct
的方法,关于此本文不再涉及,考虑放置在dealloc
相关的文章中。
因此 class_rw_t
中存储的方法、属性和协议列表都继承自 list_array_tt<Element, List>
,其中的值共有三种可能:
- 空,即没有定义任何内容;
- 一个指向单一列表的指针,即只存储了编译时(包括类本身和分类中)确定的内容;
- 一个指向列表的指针数组(类似二维数组),即存储了编译时确定以及运行时附加的内容。
class_ro_t
class_ro_t
存在于 class_rw_t
中,是只读(Read-Only)表:
// objc-runtime-new.h
// 只读表
struct class_ro_t {
// 标志位
uint32_t flags;
uint32_t instanceStart;
// 实例大小
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
// 类名
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
// 成员变量
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
而与 class_rw_t
不同的是,class_ro_t
中的方法、属性、成员变量分别直接继承自 method_list_t
、ivar_list_t
和 property_list_t
, 少了一维。这是因为只读表只存储编译时(包括类本身和分类中)确定的内容,在运行时将不会再次附加其它的数据。
为了证明上述结论,我们再简单了解一下类在首次初始化中所执行的 realizeClass
方法(注意,realizeClass
存在于 obj4-750,而在 Xcode 11.1 和 obj4-756.2 及之后该函数已被更名为 realizeClassWithoutSwift
且有部分差异,但由于主体部分未改变且变更不影响如下分析暂时略过):
// objc-runtime-new.h
// 类是未实现的 Future 类(关于 Future 类目前网上资料较少,Runtime 中有 objc_getFutureClass API,官方文档意为用作与 CoreFoundation 中类型无缝桥接(Toll-Free Bridging),比如 __NSCFArray 等,但该方法并未暴露在外界,即不允许开发者去调用)
// class is unrealized future class - must never be set by compiler
#define RO_FUTURE (1<<30)
// 类已实化
// class is realized - must never be set by compiler
#define RO_REALIZED (1<<31)
// class_t->data is class_rw_t, not class_ro_t
#define RW_REALIZED (1<<31)
// class is unresolved future class
#define RW_FUTURE (1<<30)
// objc-runtime-new.mm
/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls,
* 在类上执行首次初始化,
* including allocating its read-write data.
* 包括分配读写数据。
* Returns the real class structure for the class.
* 返回类的真实类结构。
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
runtimeLock.assertLocked();
// 只读表指针
const class_ro_t *ro;
// 可读可写表指针
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
// 若为空,则返回
if (!cls) return nil;
// 若已经实化,则返回
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
// 实化前,cls->data() 返回指向只读表的指针,因此可以强转
ro = (const class_ro_t *)cls->data();
// 判断是否为未来的类(即 RO_FUTURE 标志着读写数据是否已经被分配)
// 可读可写表和只读表的首个成员均为 flags 且 RO_FUTURE 与 RW_FUTURE 实际值相同
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
// 将数据赋值给可读可写表
rw = cls->data();
// 将可读可写表中的只读表赋值给只读表
ro = cls->data()->ro;
// 更新标记位
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 通常为该逻辑
// Normal class. Allocate writeable class data.
// 普通类,分配可写入类数据
// 分配空间
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
// 将可读可写表中的只读表指针指向只读表指针
rw->ro = ro;
// 更新标记位,实化且正在实化
rw->flags = RW_REALIZED|RW_REALIZING;
// 将可读可写表写回类中
cls->setData(rw);
}
// ---
// 以下内容与 class_rw_t 相关性不大,仅供参考
// 是否为元类
isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
// Choose an index for this class.
// 设置类的索引
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
// 打印 Debug 信息
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex());
}
// 先实化父类,再实化类 / 元类,最后实化类本身
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));
#if SUPPORT_NONPOINTER_ISA
// 通常为该逻辑
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && !(ro->flags & RO_META) &&
0 == strcmp(ro->name, "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsa(rawIsaIsInherited);
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
// 在重新映射的情况下更新超类和元类
cls->superclass = supercls;
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
// 如果是父类,且非元类,调整实例变量,可能会重新分配 class_ro_t 并更新 ro 变量
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
// 设置实例大小
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// 设置子类 / 基类
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
// 真正去连接分类
methodizeClass(cls);
return cls;
}
struct objc_class : objc_object {
// 判断类是否实化
bool isRealized() {
return data()->flags & RW_REALIZED;
}
}
method_t
method_t
是 Obj-C 中方法的本质类型:
// objc.h
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
// objc-ptrauth.h
#if __has_feature(ptrauth_calls)
// Method lists use process-independent signature for compatibility.
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
#else
using MethodListIMP = IMP;
#endif
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
method_t
中存储了方法的名称、类型和 IMP
:
Member | Notes |
---|---|
SEL name; |
方法名,又称方法选择器(Selector),同名方法即使不同类也拥有相同的 SEL 类型方法名;其底层数据类型为 struct objc_selector 但没有找到相关开源,可近似认为是 C 中 char * 字符串;可以使用 @selector() 或 sel_registerName() 方法传入方法来得到方法名;也可通过 sel_getName() 或 NSStringFromSelector() 将 SEL 类型转换为 char * 或 NSString |
const char *types; |
方法类型编码(Type Encoding),将方法的返回值、参数等信息编码的字符串;Obj-C 方法默认会有两个参数,即 id self & SEL op ,因此默认就会有 @: ,首个数字代表所有参数占用的总字节数,后续则代表每个参数的起始字节数;@encode() ;详细可见 Type Encodings - Objective-C Runtime Programming Guide |
MethodListIMP imp; |
方法实现,即方法的内存地址,类似 C 中的函数指针,可以通过该地址访问到方法的代码块进而执行 |
元类对象
元类对象和类对象一样,都是 Class
类型,但元类对象只可以通过 Runtime 的 object_getClass
API 传入类对象,获取到元类对象。需要注意的是 [[Computer class] class]
或 [[cpt class] class]
都只能返回类对象。
对于 object_getClass
,如果参数是实例对象,返回类对象;如果参数是类对象,返回元类对象;如果参数是元类对象,返回 NSObject(基类)的元类对象。class_isMetaClass
可以用来检查 Class
是否为元类对象。
VClass cptMetaVClass = (__bridge VClass)object_getClass([Computer class]);
class_rw_t *rwt = cptMetaVClass->data();
// LLDB:
// Printing description of rwt->methods->first:
// (method_t) first = {
// name = "arch"
// types = 0x0000000100000f86 "@16@0:8"
// imp = 0x0000000100000d40 (DemoOC`::+[Computer arch]() at main.mm:52)
// }
元类对象中除了存储 isa
和 superclass
指针,还存储了类方法。其存放位置和类对象存放实例方法的存放位置(方法列表)一致。
superclass
What
superclass
是仅存在于类对象和元类对象的一个成员。类对象中的 superclass
指向当前类的父类,元类对象中的 superclass
指向该元类对象的父类。基类 NSObject
的 superclass
指向 nil
,根元类对象的 superclass
指向基类 NSObject
的类对象。
isa & superclass
isa
将实例对象与类对象、元类对象连接起来;superclass
将子类、父类、基类连接起来。
当向一个实例发送实例方法的消息时(也可称调用实例方法,但因为 Obj-C 是消息结构的语言,方法调用并不严谨),该实例对象会通过 isa
找到其类对象,因为实例方法存储在类对象中;如果该类的类对象中并没有存储该实例方法,其类对象将使用 superclass
找到其父类的类对象,并在其中查找实例方法;直到在 NSObject
基类的类对象也无法找到时,superclass
将指向 nil
停止查找,抛出异常:unrecognized selector sent to instance
(注意,我们这里仅讨论消息传递,暂时忽略消息转发)。
@interface Computer : NSObject
- (void)run;
@end
@implementation Computer
@end
@interface Mac : Computer
@end
@implementation Mac
// - (void)run {
// NSLog(@"RUN");
// }
@end
// '-[Mac run]: unrecognized selector sent to instance 0x10330f6c0'
[[[Mac alloc] init] run];
当向一个类(的类对象)发送类方法的消息时,不会经过实例对象;类对象会通过其 isa
指针找到其元类对象,因为类方法存储在元类对象中;如果该类的元类对象中并没有存储该类方法,其元类对象将使用 superclass
找到其父类的元类对象,并在其中查找类方法;直到查找到 NSObject
基类的元类对象还没有该类方法时,NSObject
元类对象的 superclass
将指回 NSObject
类对象中,在其存储的实例方法列表中查找,若此时还没有找到将停止查找,并抛出异常:unrecognized selector sent to instance
,因为 NSObject
类对象的 superclass
将指向 nil
。
@interface NSObject (Extension)
@end
@implementation NSObject (Extension)
- (void)run {
NSLog(@"RUN");
}
@end
@interface Computer : NSObject
+ (void)run;
@end
@implementation Computer
@end
@interface Mac : Computer
@end
@implementation Mac
@end
// OUTPUT:
// RUN
通过上一点,其实我们可以知道在 Obj-C 中,方法是通过消息传递的,而传递的消息其实就是方法名的 SEL
,因此这也是严谨意义上 Obj-C 不支持重载(Overload)的原因(但支持参数个数不同的重载),具体可详见《Obj-C 中的重载与重写》一文。