刨根问底之OC对象本质
[toc]
我们平时编写的Objective-C代码,底层实现其实都是C\C++代码
在计算机中编译过程是
Objective-C C\C++ 汇编代码 机器语言
1.内存布局
- Objective-C的面向对象是基于C/C++的结构体实现的
- 将Objective-C代码转换为C/C++代码可以通过xcode的内置clang编译器实现
- 如果需要链接其他框架,使用-framework参数。比如-framework UIKit
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
### 不输入-O 默认生成一个名字一样的文字
比如
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"Hello, World!");
}
return 0;
}
编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
###没有-o默认生成一个名字一样的文字
生成一个main.cpp文件,文件内容太多,这里不放入了。
关键代码可以看到
//NSobject implementation NSobject的实现
struct NSObject_IMPL {
Class isa;
};
通过command+单击查看NSobject的定义为
@interface NSObject {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
//简化为
@interface NSObject {
Class isa ;
}
验证了Nsobject的本质为结构体
struct NSObject_IMPL {
Class isa;
};
Class的内容为
typedef struct objec_class *Class
class是一个指向结构体objec_class
的指针,所以isa在64系统中站占8个字节
1.1 Object对象占用字节数
按照之前的理解,一个NSobject对象应该占8个字节
但是
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 获得obj指针所指向内存的大小 :分配内存的大小>>16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
}
return 0;
}
打印结果
2020-11-19 21:13:05.329575+0800 OC本质[91718:1318885] 8
2020-11-19 21:13:05.329956+0800 OC本质[91718:1318885] 16
2020-11-19 21:13:05.330002+0800 OC本质[91718:1318885] Hello, World!
Program ended with exit code: 0
一个对象实例化后分配了16个字节,但是实际上用了8个字节。
可以从苹果源码中找到答案
https://opensource.apple.com/tarballs/objc4/
下载最新的 781查看class_getInstanceSize
可知
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
//返回类的成员变量所占的大小
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
查找过程如下
进一步验证,查看alloc源码
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//每个oc对象最少16个字节
if (size < 16) size = 16;
return size;
}
从_class_createInstanceFromZone
可知size是从instanceSize
函数来的,进行calloc
分配内存,而instanceSize
中判断了如果小于16就分配16个字节
其实一个对象实例化占的大小,跟他成员变量有关,修改编写Person类
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *name1;
@property (nonatomic, copy) NSString *name2;
@property (nonatomic, copy) NSString *name3;
@property (nonatomic, copy) NSString *name4;
@property (nonatomic, copy) NSString *name5;
@property (nonatomic, copy) NSString *name6;
@property (nonatomic, copy) NSString *name7;
@property (nonatomic, copy) NSString *name8;
@property (nonatomic, copy) NSString *name9;
@property (nonatomic, copy) NSString *name0;
@property (nonatomic, copy) NSString *name11;
@property (nonatomic, copy) NSString *name12;
@end
NS_ASSUME_NONNULL_END
查看结果
2020-11-19 22:04:06.540106+0800 OC本质[1735:19907] 112
2020-11-19 22:04:06.540600+0800 OC本质[1735:19907] 112
2020-11-19 22:04:06.540665+0800 OC本质[1735:19907] Hello, World!
Program ended with exit code: 0
实际上isa是8个字节,然后每个String是8个字节,所以总共占用的是
个字节
1.1.1 总结
系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
但是成员变量也会占用空间,实际上是对象的大小有isa占用字节和成员变量大小之和决定的
1.2 常用LLDB指令获取内存
- print、p:打印
- po:打印对象
- 读取内存
- memory read/数量格式字节数 内存地址 可以简写x
- x/数量格式字节数 内存地址
- x/3xw 0x10010
- 格式
- x是16进制(小段模式),f是浮点,d是10进制
- 字节大小
- b:byte 1字节,h:half word 2字节
- w:word 4字节,g:giant word 8字节
- 格式
- 修改内存中的值
- memory write 内存地址 数值
- memory write 0x0000010 10
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
// Person *obj = [[Person alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([Person class]));
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
NSLog(@"Hello, World!");
}
return 0;
}
利用lldb,可以看到nsobjec只占用了8个字节
1.3clang分析有成员变量的结构
@interface Person : NSObject
{
@public
int _age1;
int _age2;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *obj = [[Person alloc] init];
NSLog(@"%zd", class_getInstanceSize([Person class]));
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
NSLog(@"Hello, World!");
}
return 0;
}
打印结果
2020-11-19 22:51:41.753207+0800 OC本质[4754:73510] 16
2020-11-19 22:51:41.753646+0800 OC本质[4754:73510] 16
2020-11-19 22:51:41.753681+0800 OC本质[4754:73510] Hello, World!
Program ended with exit code: 0
clang结果为
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age1;
int _age2;
};
//NSobject implementation NSobject的实现
struct NSObject_IMPL {
Class isa;
};
此时的内存结构图类似如下,假如person的地址是0x10010
去掉一个age
#import
#import
#import
@interface Person : NSObject
{
@public
int _age1;
//int _age2;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *obj = [[Person alloc] init];
NSLog(@"%zd", class_getInstanceSize([Person class]));
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
NSLog(@"Hello, World!");
}
return 0;
}
发现还是
2020-11-19 23:01:32.460860+0800 OC本质[5383:84288] 16
2020-11-19 23:01:32.461498+0800 OC本质[5383:84288] 16
2020-11-19 23:01:32.461551+0800 OC本质[5383:84288] Hello, World!
Program ended with exit code: 0
因为内存对齐:结构体的大小必须是最大成员大小的倍数
所以必须最大成员isa大小8的倍数
1.3.1 类的属性
@interface Person : NSObject
{
@public
int _age1;
int _age2;
}
@property (nonatomic, assign) int height;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *obj = [[Person alloc] init];
NSLog(@"%zd", class_getInstanceSize([Person class]));
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
NSLog(@"Hello, World!");
}
return 0;
}
clang之后
extern "C" unsigned long OBJC_IVAR_$_Person$_height;
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age1;
int _age2;
int _height;
};
// @property (nonatomic, assign) int height;
/* @end */
// @implementation Person
static int _I_Person_height(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)); }
static void _I_Person_setHeight_(Person * self, SEL _cmd, int height) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)) = height; }
// @end
也进一步验证了属性的本质会生成一个成员下划线开头的变量,并生成get和set方法
查看打印结果为
2020-11-20 10:28:59.689581+0800 OC本质[7058:88814] 24
2020-11-20 10:28:59.690352+0800 OC本质[7058:88814] 32
2020-11-20 10:28:59.690415+0800 OC本质[7058:88814] Hello, World!
Program ended with exit code: 0
利用lldb看下,由于是小段模式,所以4*8=32字节后,才不属于person对象,所以实际上内存分配的是32个字节
(lldb) x/5xg 0x10052b7e0
0x10052b7e0: 0x001d8001000081d1 0x0000000000000000
0x10052b7f0: 0x0000000000000000 0x0000000000000000
0x10052b800: 0x00007fff76094b8b
- 数字24:好理解,因为isa占8个字节,3个数字一共占个,也就是,但是由于内存对齐的缘故,必须是8的倍数,也就是必须是24
- 数字32:实例对象需要的是24个字节,但是看上面源码calloc,也就是调用
calloc(1,24)
,系统分配的时候,由于操作系统所以必须给32个字节,理由是#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
操作系统对齐,会根据传入的大小分配是如图的数组,所以是32,观察就是必须是16的倍数
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
是在calloc源码 https://opensource.apple.com/tarballs/libmalloc/ 这里最新的是libmalloc-283.100.5 里可以看到
2 OC 对象分类
Objective-C中的对象,简称OC对象,主要可以分为3种
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
2.1 instance对象
instance
对象就是通过类alloc
出来的对象,每次调用alloc
都会产生新的instance
对象
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
- object1、object2是NSObject的instance对象(实例对象)
- 它们是不同的两个对象,分别占据着两块不同的内存
- instance对象在内存中存储的信息包括
- isa指针
- 其他成员变量
2.2 class对象
描述实例对象信息的对象,获取类对象通过class
或者runtime的object_getClass
获取
#import
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
//获取类对象
Class objectClass1 = [obj1 class];
Class objectClass2 = [obj2 class];
Class objectClass3 = [NSObject class];
Class objectClass_runtime1 = object_getClass(obj1);
Class objectClass_runtime2 = object_getClass(obj2);
NSLog(@"实例对象地址 %p %p",
obj1,
obj1);
NSLog(@"类对象地址 %p %p %p %p %p",
objectClass1,
objectClass2,
objectClass3,
objectClass_runtime1,
objectClass_runtime2
);
}
return 0;
}
打印结果
2020-11-20 15:26:36.506594+0800 oc对象类型[32655:410913] Hello, World!
2020-11-20 15:26:36.507049+0800 oc对象类型[32655:410913] 实例对象地址 0x102972be0 0x102972be0
2020-11-20 15:26:36.507206+0800 oc对象类型[32655:410913] 类对象地址 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118
Program ended with exit code: 0
-
objectClass1, objectClass2,objectClass3,objectClass_runtime1, objectClass_runtime2
都是获取的NSObject的class(类)对象 - 它们是同一个对象。由类对象地址一样可知每个类在内存中有且只有一个class对象
实例对象里存储的成员变量由于每个实例对象的成员变量值可能不一样,所以要每个实例变量都要有一份成员变量
class对象在内存中存储的信息(只需要一份即可的信息)主要包括
- isa指针
- superclass指针
- 类的属性信息(@property)、类的对象方法信息(instance method)
- 类的协议信息(protocol)、类的成员变量描述信息(ivar)(比如成员变量的类型)
- ......
简化表格如下
2.3 meta-class对象
meta-class对象是描述类对象的对象,通过runtime的object_getClass
传入类对象获取得到,并且可以通过class_isMetaClass
判断是否是元类对象
// meta-class对象,元类对象
// 将类对象当做参数传入,获得元类对象
Class objectMetaClass = object_getClass(objectClass5);
NSLog(@"元类对象 - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));
打印结果
2020-11-20 15:43:55.683923+0800 oc对象类型[34798:441311] 元类对象 - 0x7fff922ca0f0 1
注意不能通过[objectClass5 class]
获取,因为他打印的还是类对象的地址,其实不仅仅是他,[[[objectClass5 class] class] class]
不管调用多少次class方法都是类对象
- 每个类在内存中有且只有一个meta-class对象
- meta-class对象和class对象的内存结构是一样的都是
typedef struct objec_class *Class
,但是用途不一样,在内存中存储的信息主要包括- isa指针
- superclass指针
- 类的类方法信息(class method)
-
......
但是里面其他信息的字段都是空的
image.png
2.4 objc_getClass、object_getClass区别
查看runtime源码https://opensource.apple.com/tarballs/objc4/
object_getClass
/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{ //传入一个对象,下面详细介绍,这里简单说一下
//通过对象的isa指针返回
//如果传入的instance对象,则返回class对象
//如果传入的是class对象,则返回的是元类对象
//如果传入的是原来对象,则返回的是NSOBject的元类对象
if (obj) return obj->getIsa();
else return Nil;
}
objc_getClass
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
//传入字符串类名,将一个类对象返回去
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
所以区别是
- objc_getClass:传入字符串类名,将一个类对象返回去
- object_getClass:传入的一个对象,返回他的isa。具体是
- //如果传入的instance对象,则返回class对象
- //如果传入的是class对象,则返回的是元类对象
- //如果传入的是原来对象,则返回的是NSOBject的元类对象
3 isa指针和superclass指针
3.1 isa指针
- instance的isa指向class
- 当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
- class的isa指向meta-class
- 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
3.2 superclass指针
编写两个个类
@interface LYJStudent : LYJPerson
@interface LYJPerson : NSObject
则这两个类和NSObject的superclas指针为
[图片上传失败...(image-df27de-1606027544665)]
当LYJStudent
的instance
对象要调用LYJPerson的对象方法时,会先通过isa找到LYJStudent的class,然后通过superclass找到LYJPerson的class,最后找到对象方法的实现进行调用
3.2.1 meta-class(元类对象)的superclass指针
当LYJStudent
的class要调用LYJPerson
的类方法时,会先通过isa找到LYJStudent
的meta-class,然后通过superclass找到LYJPerson
的meta-class,最后找到类方法的实现进行调用
3.3 isa、superclass总结
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- class的superclass指向父类的class
- 如果没有父类,superclass指针为nil
- meta-class的superclass指向父类的meta-class
- 基类的meta-class的superclass指向基类的class,后面验证证明这个
- instance调用对象方法的轨迹
- isa找到class,方法不存在,就通过superclass找父类
- class调用类方法的轨迹
- isa找meta-class,方法不存在,就通过superclass找父类
举例化:上面的LYJStudent
就是图中的Subclass,LYJPerson
就是图中的Superclass,NSObject
就是图中的Root Class,代入就比较好理解
- meta-class的isa指向基类的meta-class
- LYJStudent、LYJPerson和NSObject的元类对象的isa都是指向NSobject的元类对象
- class的superclass指向父类的class
- LYJStudent的类对象superclass指向LYJPerson的类对象,LYJPerson的类对象superclass指向NSObject的类对象,NSObject没有父类,则指向的是nil
- meta-class的superclass指向父类的meta-class
- LYJStudent的元类对象superclass指向LYJPerson的元类对象,LYJPerson的元类对象superclass指向NSObject的元类对象,NSObject的元类对象superclass指向NSObject的类对象(后面验证证明这个)
3.3.1 isa和superclass细节问题
编写如下代码
- 原始代码
#import
#import "NSObject+Test.h"
@interface LYJPerson : NSObject
+ (void)test;
@end
@implementation LYJPerson
+ (void)test
{
NSLog(@"+[LYJPerson test] - %p", self);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"[LYJPerson class] - %p", [LYJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[LYJPerson test];
[NSObject test];
}
return 0;
}
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (Test)
+ (void)test;
@end
NS_ASSUME_NONNULL_END
#import "NSObject+Test.h"
@implementation NSObject (Test)
+ (void)test
{
NSLog(@"+[NSObject test] - %p", self);
}
- (void)test
{
NSLog(@"-[NSObject test] - %p", self);
}
@end
打印为
2020-11-21 11:39:30.542278+0800 isa和superclass[10995:164119] [LYJPerson class] - 0x100008190
2020-11-21 11:39:30.542675+0800 isa和superclass[10995:164119] [NSObject class] - 0x7fff96159118
2020-11-21 11:39:30.542727+0800 isa和superclass[10995:164119] +[LYJPerson test] - 0x100008190
2020-11-21 11:39:30.542766+0800 isa和superclass[10995:164119] +[NSObject test] - 0x7fff96159118
Program ended with exit code: 0
可以看到[LYJPerson test]
执行时,test方法的方法调用者是LYJPerson
类对象,[NSObject test]
执行时,test方法的方法调用者是NSObject
的类对象
- 当注释掉
LYJPerson
的test
方法
@interface LYJPerson : NSObject
+ (void)test;
@end
@implementation LYJPerson
//+ (void)test
//{
// NSLog(@"+[LYJPerson test] - %p", self);
//}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"[LYJPerson class] - %p", [LYJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[LYJPerson test];
[NSObject test];
}
return 0;
}
打印为
2020-11-21 11:40:12.591616+0800 isa和superclass[11038:164790] [LYJPerson class] - 0x100008170
2020-11-21 11:40:12.592032+0800 isa和superclass[11038:164790] [NSObject class] - 0x7fff96159118
2020-11-21 11:40:12.592112+0800 isa和superclass[11038:164790] +[NSObject test] - 0x100008170
2020-11-21 11:40:12.592158+0800 isa和superclass[11038:164790] +[NSObject test] - 0x7fff96159118
Program ended with exit code: 0
可以看到[LYJPerson test]
执行时,test方法时先去LYJPerson
对象查找,发现没有就去NSObject
查找,结果找到了test方法,结果调用了NSObject
test方法,但是方法调用者还是LYJPerson类对象,[NSObject test]
执行时,test方法的方法调用者是NSObject
的类对象
- 当把
NSObject
的类方法test
也注释掉
#import "NSObject+Test.h"
@implementation NSObject (Test)
//+ (void)test
//{
// NSLog(@"+[NSObject test] - %p", self);
//}
- (void)test
{
NSLog(@"-[NSObject test] - %p", self);
}
@end
打印为
2020-11-21 11:40:43.127454+0800 isa和superclass[11080:165724] [LYJPerson class] - 0x100008150
2020-11-21 11:40:43.127854+0800 isa和superclass[11080:165724] [NSObject class] - 0x7fff96159118
2020-11-21 11:40:43.127901+0800 isa和superclass[11080:165724] -[NSObject test] - 0x100008150
2020-11-21 11:40:43.127939+0800 isa和superclass[11080:165724] -[NSObject test] - 0x7fff96159118
Program ended with exit code: 0
可以看到[LYJPerson test]
执行时,test方法时先去LYJPerson
对象查找,发现没有就去NSObject
的元类对象中查找,结果没找到找到test方法,按照上面说的就去,通过superclass
去NSObject
的类对象里找,找到了test方法,结果执行了类对象的test方法,也就是实例方法
但是方法调用者还是LYJPerson类对象,[NSObject test]
执行时,跟[LYJPerson test]
类似
验证了上面说的第三点,NSObject的元类对象superclass指向NSObject的类对象
oc对象的方法调用都是消息发送机制,转换成objc_msgSend(方法调用者,@selector(test))
3.3.2 isa细节问题
编写测试代码
#import
#import
// LYJPerson
@interface LYJPerson : NSObject
{
@public
int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation LYJPerson
- (void)test
{
}
- (void)personInstanceMethod
{
}
+ (void)personClassMethod
{
}
- (id)copyWithZone:(NSZone *)zone
{
return nil;
}
@end
// LYJStudent
@interface LYJStudent : LYJPerson
{
@public
int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation LYJStudent
- (void)test
{
}
- (void)studentInstanceMethod
{
}
+ (void)studentClassMethod
{
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// LYJStudent *student = [[LYJStudent alloc] init];
LYJPerson *person = [[LYJPerson alloc] init];
Class personClass = [LYJPerson class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
lldb
(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb)
发现person->isa和person的类对象不一样
这是因为
从64bit开始,isa需要进行一次位运算,才能计算出真实地址
isa指向的地址&ISA_MASK 得到对应的地址
ISA_MASK
的值如下
查看源码https://opensource.apple.com/tarballs/objc4/
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 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
验证一下
还是上面的测试代码,由于我的电脑是intel的x86架构,不是M1的arm架构,所以执行& 0x00007ffffffffff8ULL
执行 lldb如下
(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb) p/x 0x001d800100008461 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100008460
(lldb)
可看到得到的结果0x0000000100008460
跟personClass一致 得到了验证
进一步验证类对象的isa
(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb) p/x 0x001d800100008461 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100008460
(lldb) p/x personClass->isa
error: :1:12: member reference base type 'Class' is not a structure or union
personClass->isa
~~~~~~~~~~~^ ~~~
(lldb)
发现获取不到isa
因为class的结构是typedef struct objec_class *Class
而objec_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;
/* Use `Class` instead of `struct objc_class *` */
所以可以自定义objec_class,让类对象指向这个自定义的
struct lyj_objc_class {
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// LYJStudent *student = [[LYJStudent alloc] init];
LYJPerson *person = [[LYJPerson alloc] init];
Class personClass = [LYJPerson class];
struct lyj_objc_class *personClass2 = (__bridge struct lyj_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
然后进行lldb可得到
(lldb) p/x personClass
(Class) $0 = 0x0000000100008460 LYJPerson
(lldb) p/x personClass2
(lyj_objc_class *) $1 = 0x0000000100008460
(lldb) p/x personClass2->isa
(Class) $2 = 0x0000000100008438
(lldb) p/x personMetaClass
(Class) $3 = 0x0000000100008438
(lldb) p/x 0x0000000100008438 &0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x0000000100008438
(lldb)
发现类对象的isa
也是&ISA_MASK
得到他的元类对象
3.3.3 superclass细节问题
还是上面的代码,看下superclass的地址是什么
struct lyj_objc_class {
Class isa;
Class _Nullable super_class;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// LYJStudent *student = [[LYJStudent alloc] init];
LYJPerson *person = [[LYJPerson alloc] init];
Class personClass = [LYJPerson class];
struct lyj_objc_class *personClass2 = (__bridge struct lyj_objc_class *)(personClass);
Class studentClass = [LYJStudent class];
struct lyj_objc_class *studentClass2 = (__bridge struct lyj_objc_class *)(studentClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
然后执行可知
(lldb) p/x personClass
(Class) $3 = 0x0000000100008468 LYJPerson
(lldb) p/x studentClass2->super_class
(Class) $4 = 0x0000000100008468 LYJPerson
(lldb)
可以验证类对象的super_class就是指向父类的类对象
4.窥探struct objc_class的结构
下载苹果源码
https://opensource.apple.com/tarballs/objc4/
由于isa是class类型,class类型结构又是typedef struct objec_class *Class
窥探struct 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;
/* Use `Class` instead of `struct objc_class *` */
可看到上部分代码在 OBJC2已经过时
现在 OBJC2是
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
..................
}
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
..................
}
可以简化为
struct objc_class {
Class ISA;
Class superclass;
cache_t cache; // 方法缓存 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 用于获取具体的类信息
..................
}
通过
class_rw_t *data() const {
return bits.data();
}
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
.........
//方法列表
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->methods;
} else {
return method_array_t{v.get()->baseMethods()};
}
}
//属性列表
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->properties;
} else {
return property_array_t{v.get()->baseProperties};
}
}
//协议列表
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->protocols;
} else {
return protocol_array_t{v.get()->baseProtocols};
}
}
//只读的信息
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is())) {
return v.get()->ro;
}
return v.get();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is()) {
v.get()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
得到
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;
具体的关系如下图