前言
书接上回isa结构分析,我们得知,对象的isa指针指向类,确切的说,是isa指针的shiftcls
位域中指向类对象Class
的地址。那OC层的Class
在底层C、C++层面里,所对应的结构体里,有哪些成员变量和函数呢?下面将大致分析下类的底层结构。
isa指向
首先我们来看看isa指针的指向问题,我们都知道,对象的isa指针
指向类对象Class,那类对象Class的isa指针
指向哪里呢?我们先通过在lldb中打印地址来查看详情。
我们先定义一个类LGPerson
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@end
NS_ASSUME_NONNULL_END
//--------------------分割线----------------------
#import "LGPerson.h"
@implementation LGPerson
@end
在程序入口main.m
中生成LGperson
对象
#import <Foundation/Foundation.h>
#import "LGPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [[LGPerson alloc] init];
}
return 0;
}
打断点查看person对象的地址信息
(lldb) po person
<LGPerson: 0x10124dcb0>
(lldb) p person
(LGPerson *) $2 = 0x000000010124dcb0
我们常用的LLDB
指令po,p
,他俩的区别如下:
指令名称 | 释义 |
---|---|
p | 是 expression 的别名,打印某个东西,可以是变量和表达式 |
po | 一般用于打印对象,是 expression -O 的别名,会输出对应的值 |
再来看看另外两个LLDB指令
指令名称 | 释义 |
---|---|
p/x | 以16进制形式读取对象的地址或者值 |
x/4gx | 以16进制形式读取4段8位的内存空间里面存储的地址或者值 |
用上面指令看看person对象
的地址
(lldb) p/x person
(LGPerson *) $4 = 0x000000010124dcb0
(lldb) x/4gx person
0x10124dcb0: 0x001d8001000020e9 0x0000000000000000
0x10124dcc0: 0x0000000080080000 0x00007fff8d07d208
首先第一段0x10124dcb0: 0x001d8001000020e9
这个就是person
对象的isa指针
地址,再查看isa指针
中位域shiftcls
的值,有个小技巧,通过和ISA_MASK
进行与运算
,过滤出shiftcls
的地址值
在查看isa_t结构体中,可以找到ISA_MASK的宏定义(以x86_64为例)
define ISA_MASK 0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000020e9 & 0x00007ffffffffff8ULL
(unsigned long long) $6 = 0x00000001000020e8
(lldb) po $6
LGPerson
验证得出:person对象
的isa指针
的位域shiftcls
中指向的是类LGPerson
的地址。
同理,我们在继续看看类LGPerson
对象的isa指针位域shiftcls
中存放的什么值?
// 以4段方式查看LGPeson类对象地址
(lldb) x/4gx $6
0x1000020e8: 0x00000001000020c0 0x00000001003ef190
0x1000020f8: 0x0000000101063830 0x0004801000000007
// 首地址isa & ISA_MASK,得到位域shiftcls中存放的值
(lldb) p/x 0x00000001000020c0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00000001000020c0
// 打印shiftcls值
(lldb) po $7
LGPerson
发现,类对象LGPerson
的isa指针中shiftcls
存储的值也是LGPerson
,但是$6 = 0x00000001000020e8
与$7 = 0x00000001000020c0
的地址值显然不同,说明$7
明显不是指向类对象LGPerson
它自己,但是打印出的值是LGPerson
,这就是苹果提出的一个概念元类(meta class)
。
验证得出,类对象的isa指向与对象的isa指向不同,对象的isa指向类,而类的isa指向元类(苹果爸爸定义为meta class)。
接着我们继续查看$7
的isa指针的位域shiftcls
中存放的地址值是什么?
(lldb) x/4gx $7
0x1000020c0: 0x00000001003ef140 0x00000001003ef140
0x1000020d0: 0x0000000101063b40 0x0003e03100000007
(lldb) p/x 0x00000001003ef140 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x00000001003ef140
(lldb) po $8
NSObject
$7
指针的位域shiftcls
中存放的地址$8
值是NSObject
。接着看$8
的shiftcls
的值,如下:
(lldb) x/4gx $8
0x1003ef140: 0x00000001003ef140 0x00000001003ef190
0x1003ef150: 0x0000000100640ff0 0x0004e03100000007
(lldb) p/x 0x00000001003ef140 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001003ef140
(lldb) po $9
NSObject
$8 = 0x00000001003ef140
与 0x1003ef140: 0x00000001003ef140
(isa指针地址)一样,shiftcls
打印出的地址也是0x00000001003ef140
,值也是NSObject,说明$8
的isa指针
指向自己。
那这个$9
的值NSObject
,是不是我们通常的基类NSObject类呢?接下来验证一下
(lldb) p/x NSObject.class
(Class) $10 = 0x00000001003ef190 NSObject
$10
的地址与$9
不同,说明不是基类。
验证得出,元类的isa指针指向NSObject根元类(苹果爸爸定义为root meta class)。
上一张isa指向
经典图:
以上通过LLDB
中的指令查看地址,得出isa
指针的指向是:
对象 --> 类 --> 元类 --> 根元类-->根元类 指向自身
。
特殊问题:类信息在内存中存在几份
以上我们得知,类对象的isa指针指向元类,那类对象Class的isa指针指向的地址是同一个还是不同的?我们还是用代码验证一下:
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
NSLog(@"\n%p\n%p\n%p", class1, class2, class3);
编译运行后得出:
2020-09-14 17:23:07.926664+0800 KCObjc[9752:305959]
0x100002100
0x100002100
0x100002100
地址一模一样,说明类信息在内存中只存在一份。
类Class结构分析
言归正传,分析了isa指针指向问题后,现在再来回头看看Class结构的具体情况:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
Class
对应底层结构体是objc_class
,再来看看objc_class
:
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() const {
return bits.data();
}
// 省略部分代码.......
}
-
objc_class
继承objc_object
-
superclass
是父类Class -
cache_t cache
缓存 -
class_data_bits_t bits
成员
cache_t结构体
// 只列举了成员变量
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
}
// 省略部分代码...
-
CACHE_MASK_STORAGE_OUTLINED
这种缓存模式,则占用内存大小是:bucket_t
+mask_t
+uint16_t
+uint16_t
-
CACHE_MASK_STORAGE
这种缓存模式,则占用内存大小是:uintptr_t
+mask_t
+uint16_t
+uint16_t
uintptr_t内存大小
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
uintptr_t
是long类型,8个字节
bucket_t内存大小
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
// 省略部分代码...
}
不管是__arm64__
还是其它,都是uintptr_t
+_sel
指针 --> 4 + 8 = 12
mask_t内存大小
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
一目了然,即整型,大小为4。
综上所述,
-
CACHE_MASK_STORAGE_OUTLINED
模式下,explicit_atomic<struct bucket_t *> _buckets
是结构体bucket_t
的指针 8,+mask_t
4 + 2 *uint16_t
2 = 16 -
CACHE_MASK_STORAGE
模式下是8+4+2+2,也是16;
结论cache_t 所占内存大小:16
重点 class_data_bits_t
结构体class_data_bits_t
大致分为几个部分:
-
bits
操作相关,为私有函数,包含获取、设置、清空等操作
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
{
// ...省略部分代码
}
void setBits(uintptr_t set) {
__c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
}
void clearBits(uintptr_t clear) {
__c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
}
2.重要部分data()
,返回结构体class_rw_t *
指针类型,里面存放了属性列表,实例方法列表,协议列表等信息
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData) {
// ...省略部分代码
}
3.ro data
相关
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() {
// ...省略部分代码
}
void setClassArrayIndex(unsigned Idx) {
#if SUPPORT_INDEXED_ISA
// 0 is unused as then we can rely on zero-initialisation from calloc.
ASSERT(Idx > 0);
data()->index = Idx;
#endif
}
unsigned classArrayIndex() {
#if SUPPORT_INDEXED_ISA
return data()->index;
#else
return 0;
#endif
}
4.swift相关
bool isAnySwift() {
return isSwiftStable() || isSwiftLegacy();
}
bool isSwiftStable() {
return getBit(FAST_IS_SWIFT_STABLE);
}
void setIsSwiftStable() {
setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
}
bool isSwiftLegacy() {
return getBit(FAST_IS_SWIFT_LEGACY);
}
void setIsSwiftLegacy() {
setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
}
// fixme remove this once the Swift runtime uses the stable bits
bool isSwiftStable_ButAllowLegacyForNow() {
return isAnySwift();
}
_objc_swiftMetadataInitializer swiftMetadataInitializer() {
// This function is called on un-realized classes without
// holding any locks.
// Beware of races with other realizers.
return safe_ro()->swiftMetadataInitializer();
}
class_rw_t内部信息
class_rw_t
里存放了类的实例方法列表,属性列表,协议列表等信息,如何查看呢?下面举例说明。
首先在LGPerson
类中添加属性和方法
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) NSString *nikeName;
- (void)sayHello;
+ (void)sayByebye;
@end
查看LGPerson
类的地址
(lldb) p/x LGPerson.class
(Class) $0 = 0x00000001000021f8
// 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
根据上面objc_class
的结构分析,要获取class_data_bits_t bits
的地址,需要位移isa
8位+Class
8位 + cache_t
16位 = 32位
0x00000001000021f8 + 0x20 = 0x0000000100002218
获取class_data_bits_t
(lldb) p (class_data_bits_t *)0x0000000100002218
(class_data_bits_t *) $1 = 0x0000000100002218
获取data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000101142b80
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975608
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
1. 查看属性列表信息
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002188
arrayAndFlag = 4294975880
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x0000000100002188
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
count = 2
说明有2个属性,再仔细查看这两个属性:
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "nikeName", attributes = "T@\"NSString\",C,N,V_nikeName")
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file /Users/Aron/objc4_debug/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
name
nikeName
都有了,当获取第三个时越界,Assertion failed: (i < count)
表示下标必须小于count:2
2. 查看方法列表信息
同理,查看方法列表methods()
(lldb) p $3.methods()
(const method_array_t) $9 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020c0
arrayAndFlag = 4294975680
}
}
}
(lldb) p $9.list
(method_list_t *const) $10 = 0x00000001000020c0
(lldb) p *$10
(method_list_t) $11 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 5
first = {
name = "nikeName"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000e20 (KCObjc`-[LGPerson nikeName])
}
}
}
count = 5
说明有5个方法,明明我只定义了2个方法,- (void)sayHello;
和+ (void)sayByebye;
,再一一打印看看是哪5个方法:
(lldb) p $11.get(0)
(method_t) $12 = {
name = "nikeName"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000e20 (KCObjc`-[LGPerson nikeName])
}
(lldb) p $11.get(1)
(method_t) $13 = {
name = "setNikeName:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000e50 (KCObjc`-[LGPerson setNikeName:])
}
(lldb) p $11.get(2)
(method_t) $14 = {
name = ".cxx_destruct"
types = 0x0000000100000f90 "v16@0:8"
imp = 0x0000000100000d90 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $11.get(3)
(method_t) $15 = {
name = "name"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000dd0 (KCObjc`-[LGPerson name])
}
(lldb) p $11.get(4)
(method_t) $16 = {
name = "setName:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000df0 (KCObjc`-[LGPerson setName:])
}
原来是2个属性的get set
方法和一个析构函数cxx_destruct
,那定义的2个方法去哪了?
补坑:
原来之前只定义了方法,并未实现sayHello
sayByebye
这2个方法。。。
先补上方法实现,重新编译
@implementation LGPerson
- (void)sayHello {
}
+ (void)sayByebye {
}
@end
再打印方法列表看看:
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d60 (KCObjc`-[LGPerson sayHello])
}
}
}
发现count = 6,不是7个
,看看少了哪个?
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d60 (KCObjc`-[LGPerson sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = "nikeName"
types = 0x0000000100000f49 "@16@0:8"
imp = 0x0000000100000e00 (KCObjc`-[LGPerson nikeName])
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "setNikeName:"
types = 0x0000000100000f51 "v24@0:8@16"
imp = 0x0000000100000e30 (KCObjc`-[LGPerson setNikeName:])
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = ".cxx_destruct"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $6.get(4)
(method_t) $11 = {
name = "name"
types = 0x0000000100000f49 "@16@0:8"
imp = 0x0000000100000db0 (KCObjc`-[LGPerson name])
}
(lldb) p $6.get(5)
(method_t) $12 = {
name = "setName:"
types = 0x0000000100000f51 "v24@0:8@16"
imp = 0x0000000100000dd0 (KCObjc`-[LGPerson setName:])
}
发现少了sayByebye
,这是类方法,根据isa指针走位分析,实例方法存储在类中,那类方法应该存储在元类中
。去元类
里看看:
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100002230
(lldb) x/4gx 0x0000000100002230
0x100002230: 0x0000000100002208 0x00000001003f0190
0x100002240: 0x000000010142d880 0x0001802400000003
类 LGPerson
的isa指针
地址是0x0000000100002208
,偏移32位获取class_data_bits_t
,即0x0000000100002228
,再打印出这个地址对应的方法列表信息:
(lldb) p (class_data_bits_t *)0x0000000100002228
(class_data_bits_t *) $1 = 0x0000000100002228
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010142d7e0
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975536
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8dcd5c60
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002078
arrayAndFlag = 4294975608
}
}
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002078
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayByebye"
types = 0x0000000100000f35 "v16@0:8"
imp = 0x0000000100000d50 (KCObjc`+[LGPerson sayByebye])
}
}
}
count = 1
只有1个方法sayByebye
。给自己一个666,哈哈!印证了isa的走位!
补充:objc_class继承objc_object
struct objc_class : objc_object {
// 省略部分代码.......
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
分析:
- 结构体类型
objc_class
继承自objc_object
类型,其中objc_object
也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性;
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
-
NSObject
中的isa在底层是Class
类型,而Class
的底层编码来自objc_class
类型,所以NSObject
也拥有了isa指针
; -
由
NSObject
类初始化一个实例对象objc,objc 满足 objc_object 的特性(即有isa指针
),主要是因为isa
是NSObject
从objc_class
继承过来的,而objc_class
继承自objc_object
,objc_object
有isa指针
。所以对象都有一个isa
,isa
表示指向,来自于当前的objc_object
; -
objc_object
就好比一个模板,不论是Class
对象还是继承NSObject
的对象,都最终指向继承objc_object
(实质是继承了成员isa
),所以说万物皆对象。
总结
1.首先从底层源码分析了isa
指针的走位,并且通过示例得以证明。
2.通过isa
的走位,我们分析了类Class
的底层结构objc_class
,通过示例打印属性列表信息和方法列表信息,证明了实例方法存储在类中,类方法存储在元类中
。
3.最后通过objc_class
与objc_object
的继承关系,得出万物皆对象,但始终离不开isa
。
网友评论