[Runtime] Class, Object 与 isa
Table of Contents
作为 Runtime 系列的第一篇博文(也是 2022 年第一篇),先从最基础的对象、类开始学习。
参考
OC 编译过程 我们可以借助 clang
工具将 OC 源码预处理成 CPP 来窥探 OC 的内部实现。
定义一个简单的类:
@interface Foo : NSObject
@property (nonatomic, assign) BOOL ppty1;
@property (nonatomic, copy) NSString *ppty2;
- (BOOL)iMethod1;
- (void)iMethod2:(NSString *)p1;
+ (void)cMethod1;
@end
通过命令 clang -rewrite-objc xx.m
得到一个 9 万多行的 cpp 文件 😱
https://gist.github.com/ryderfang/75a852f1ae0961aeea97127f7d9ca6c5
TL;DR #
太长不看版:
🧸 objc_object 是所有实例对象的底层结构,内部只有一个 isa
🎁 objc_class 是所有类/元类对象的底层结构,它也有一个 isa 指针,指向自己的元类
OC 1.0 | OC 2.0 | |
---|---|---|
id 实例 | typedef struct objc_object *id; | typedef struct objc_object *id; |
objc_object | struct objc_object {} | struct objc_object {} |
Class 类 | typedef struct objc_class *Class; | typedef struct objc_class *Class; |
objc_class | struct objc_class {} | struct objc_class : objc_object {} |
Class 是什么 #
Class 1.0 #
在 <objc/objc.h> 中有这样一行:
typedef struct objc_class *Class;
在 iOS 15 之前 <objc/runtime.h> 中曾经有 objc_class
的定义:
https://opensource.apple.com/source/objc4/objc4-750/runtime/runtime.h.auto.html
/* Types */
#if !OBJC_TYPES_DEFINED
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;
/* Use `Class` instead of `struct objc_class *` */
#endif
目前我使用的 Xcode 13.2.1 中已经找不到这个定义,也就是说在 macOS 12.1 / iOS 15.2 中,OBJC1 已经被完全废弃。
Hint: 按照官方文档 Runtime Version 的解释, legacy 版本 (OC 1.0) 在 2007 年就完全废弃了,所有 iPhone 和 OSX 10.5 以后的 Mac 上使用的都是 modern 版本 (OC 2.0)!
根据 wiki 的说明, OC 2.0 于 2006 年发布,而 iPhone 第一代 是在 2007 年发布的, Mac OS X 10.5 Leopard 也是在 2007 年上市。
从这个宏 OBJC2_UNAVAILABLE
可以看出来 OBJC2 这种定义会失效。那么 OBJC2 是什么?
Class 2.0 #
在 objc-runtime-new.h 中定义了 objc_class 的 2.0 版本:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
}
了解到,苹果在 2006 年重写了 runtime,发布了新的 Runtime 2.0,那么 __OBJC2__
这个宏是什么时候生效的呢?
通过查找苹果公开的 runtime 源码:https://opensource.apple.com/source/objc4/ 发现:
从
objc4-750 这个版本开始,在
objc-config.h 中开始有了 __OBJC2__
的定义!
// Define __OBJC2__ for the benefit of our asm files.
#ifndef __OBJC2__
# if TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__
// old ABI
# else
# define __OBJC2__ 1
# endif
#endif
来自 apple 的 commit:
https://github.com/apple-oss-distributions/objc4/commit/26c7408b94ead1f04a0b5976e354a03966ce61ea
在
objc-api.h 中定义了 OBJC_ISA_AVAILABILITY
:
/* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable
* in the future */
#if !defined(OBJC_ISA_AVAILABILITY)
# if __OBJC2__
# define OBJC_ISA_AVAILABILITY __attribute__((deprecated))
# else
# define OBJC_ISA_AVAILABILITY /* still available */
# endif
#endif
在
objc-private.h 中同时定义了 OBJC_TYPES_DEFINED
:
/* Isolate ourselves from the definitions of id and Class in the compiler
* and public headers.
*/
#ifdef _OBJC_OBJC_H_
#error include objc-private.h before other headers
#endif
#define OBJC_TYPES_DEFINED 1
同时也定义了 Class 和 id
typedef struct objc_class *Class;
typedef struct objc_object *id;
所以,不管 OC 1.0 还是 2.0,
Class 都是一个叫
objc_class
的结构体指针。
Object 与 id #
在 <objc/objc.h> 中,定义
https://opensource.apple.com/source/objc4/objc4-750/runtime/objc.h.auto.html
Object 1.0 #
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
同样被包在 OBJC_TYPES_DEFINED
宏中,也就是 OC 2.0 并不生效。
那么,OC 2.0 的 objc_object
是什么样的呢,在
objc-private.h 中定义了:
Object 2.0 #
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// 省略大量方法
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
// 省略大量方法`
};
可以看出来不管 OC 1.0 还是 2.0,
objc_object 都是一个包含着一个叫 isa 指针/变量 的对象。
isa 又是什么 #
在 1.0 版本中 isa 就是一个 Class 对象,也就是一个指向 objc_class
的指针,而在 2.0 时,isa 是一个叫 isa_t
的结构体变量。
那么 isa_t
是什么呢?
同样在 objc-private.h 中有定义:
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);
};
参考 1 的图:
作为一个 union 结构,与 struct 区别在于,成员之间会互相覆盖,union 的总内存占用等于最大的成员占用的内存大小,而 struct 大小至少是成员内存占用之和,如果需要字节对齐则会更大。
所以采用 union 结构可以节省内存。
在
objc-object.h 中,有 objc_object::initIsa()
的实现:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
可以看出 isa_t 中的 Class 与 bits 是互斥的,避免了互相覆盖的问题。