一个NSObject对象占用多少内存.
我们平时编写的Objective-C代码,底层实现其实都是C\C++代码.
因为C语言不能通过写一个函数,去跳转到任意的指针,汇编可以利用寄存器实现,C语言使用的是“静态绑定”,也就是说,在编译时就能决定运行时所应调用的函数,如果待调用的函数地址无法硬编码在指令之中,那就要在运行期读取出来,使用“动态绑定”。而c语言是面向过程,由编译器进行处理,显然无法实现这样的需求。在运行的时候会进行特殊操作访问不同的内存空间,这就用到了runtime运行时,因此oc具备动态特性。
1.将Object-C 语言转换为C++:
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
@autoreleasepool {
YJPerson * P = [YJPerson new];
[P run];
}
//编译后 (环境依赖部分代码暂不考虑 此处没有粘贴出来)
#pragma clang assume_nonnull begin
#ifndef _REWRITER_typedef_YJPerson
#define _REWRITER_typedef_YJPerson
typedef struct objc_object YJPerson;
typedef struct {} _objc_exc_YJPerson;
#endif
struct YJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //等价于 Class isa
};
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
YJPerson * P = ((YJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YJPerson"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)P, sel_registerName("run"));
}
void runImp (id self ,SEL _cmd){
}
将.m文件转换为C++文件 即可得出
- 对象的本质:结构体
- 结构体占用内存大小
1.这个结构体只有一个成员,isa指针,而指针在64位架构中占8个字节。也就是说一个NSObjec对象所占用的内存是8个字节
2.NSObject对象中还有很多方法,这些方法也占用内存空间,但是这些方法所占用的存储空间并不在NSObject对象中。
代码获取对象占用内存的大小
// 源码
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
一个NSObject对象占用多少内存: NSObjcet实际上是只有一个名为isa的指针的结构体,因此占用一个指针变量所占用的内存空间大小,如果64bit占用8个字节,如果32bit占用4个字节。
class_getInstanceSize([self class])
- 内存结构
YGPerson * p1 = [YGPerson alloc];
p1.age = 10;
p1.nickName = @"123456";
WeChatbcce812f213eca2d008990181f56c533.png
实时查看内存数据有俩种方式
方式一:通过打断点。 Debug Workflow -> viewMemory address中输入p1的地址
方式二:通过lldb指令xcode自带的调试器
OC的类信息
- instance对象(实例对象)
- isa指针
- 其他成员变量的值
- 实例对象(不包含实现方法)
- class对象(类对象)
1.每个类在内存中有且只有一个class对象
验证方式1
NSObject *obj1 = [[NSObject alloc] init];
Class objClass1 = [obj1 class];
Class objClass2 = [NSObject class];
//class 方法返回的一直是class对象,类对象,而不是元类对象
Class objClass3 = [[NSObject class] class];
Class objClass4 = object_getClass(obj1);
NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
//0x7fff80030660-0x7fff80030660-0x7fff80030660-0x7fff80030660
验证方式2
WeChatc016b0f33f7d8addace59ddb45efb760.pngstruct 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;
- isa指针
- superclass指针
- 类的属性信息(@property)
- 类的成员变量信息(ivar)
- 类的对象方法信息(instance method),类的协议信息(protocol)
- 缓存 objc_cache
...
2.meta-class对象
- meta-class对象(元类对象)
Class metaClass = object_getClass([NSObject class]);
- isa指针
- superclass指针
- 类的类方法信息(class method)
- ......
总结isa、superclass
isa、superclass
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基类的meta-class,基类的isa指向自己
class的superclass指向父类的class,如果没有父类,superclass指针为nil
meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
instance调用对象方法的轨迹,isa找到class,方法不存在,就通过superclass找父类
class调用类方法的轨迹,isa找meta-class,方法不存在,就通过superclass找父类
- 代码验证1:
YGPerson *instanceObj = [[YGPerson alloc] init];
// 第一条线 (instance的isa值是Class对象)
Class classCls = object_getClass(instanceObj);
// 第二条线 (Class对象的isa值是meta-class)
Class metaCls = object_getClass(classCls);
// 第三条线 (meta-class的Superclass 是 RootClass的meta-class)
Class rootMetaCls0 = class_getSuperclass(metaCls);
// 第四条线 (meta-class的isa值是RootClass 的meta-class)
Class rootMetaCls1 = object_getClass(metaCls);
//RootClass的meta-class
Class rootMetaCls = object_getClass(rootMetaCls0);
NSLog(@"\n instanceObj = %p \n classCls = %p \n metaCls = %p \n rootMetaCls0 = %p \n rootMetaCls1 = %p \n rootMetaCls = %p\n", instanceObj, classCls, metaCls, rootMetaCls0, rootMetaCls1, rootMetaCls);
NSLog(@"\n instanceObj-isa = %p \n classCls-isa = %p \n metaCls-isa = %p \n rootMetaCls0-isa = %p \n", [instanceObj valueForKey:@"isa"], [classCls valueForKey:@"isa"], [metaCls valueForKey:@"isa"], [rootMetaCls0 valueForKey:@"isa"]);
2021-08-17 01:19:37.463948+0800 OC对象原理[32945:2611851]
instanceObj = 0x6000037f7e00
classCls = 0x100ac1790
metaCls = 0x100ac1768
rootMetaCls0 = 0x7fff80030638
rootMetaCls1 = 0x7fff80030638
rootMetaCls = 0x7fff80030638
2021-08-17 01:19:38.342631+0800 OC对象原理[32945:2611851]
instanceObj-isa = 0x100ac1790
classCls-isa = 0x100ac1768
metaCls-isa = 0x7fff80030638
rootMetaCls0-isa = 0x7fff80030638
- lldb验证2:
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
# endif
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
// 重要运算符
clsbits &= ISA_MASK;
// 重要 洪
# define ISA_MASK 0x007ffffffffffff8ULL
这里要验证 需要lldb指令 打印地址 这些地址只是编译优化后的地址,
获取真地址需要 &p & ISA_MASK 运算。
以上是 objc源代码
面试题总结:
一个NSObject对象占用多少内存?
答:一个指针变量所占用的大小(64bit占8个字节,32bit占4个字节)
对象的isa指针指向哪里?
instance对象的isa指针指向class对象,class对象的isa指针指向meta-class对象,meta-class对象的isa指针指向基类的meta-class对象,基类自己的isa指针也指向自己。
OC的类信息存放在哪里?
成员变量的具体值存放在instance对象。对象方法,协议,属性,成员变量信息存放在class对象。类方法信息存放在meta-class对象。
网友评论