内容简介:我们平时编写的Objetcive-C,底层实现都是C/C++实现的以使用指令
面试驱动技术之 - 带着面试题来找答案
- 一个NSObject 对象,占用多少内存
- 对象方法 与 类方法的存放在哪
- 什么是isa指针
- 什么是meta-class
- megsend 是如何找到方法的
@implementation MNSubclass - (void)compareSelfWithSuperclass{ NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); } @end 复制代码
- 输出的结果是什么
- 。。。
友情tips:如果上诉问题你都知道答案,或者没有兴趣知道,就可以不用继续往下看了,兴趣是最好的老师,如果没有兴趣知道这些,往下很难读得进去~
OC对象的本质
我们平时编写的Objetcive-C,底层实现都是C/C++实现的
- 问 : Objetcive-C 基于 C/C++ 实现的话,Objetcive-C 对象相当于C/C++ 中的什么数据结构呢?
@interface MNPerson : NSObject { int _age; double _height; NSString *name; } 复制代码
以 MNPerson
为例,里面的成员变量有不同类型是,比如 int
、 double
、 NSString
类型,假如在C/C++ 中用 数组
存储,显然是不太合理的
- 答: C/C++中用
结构体
的数据格式,表示oc对象。
// 转成c/c++ 代码后,MNPerson 的结构如下 struct NSObject_IMPL { Class isa; }; struct MNPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; int _age; double _height; NSString *name; }; 复制代码
使用指令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc oc源文件 -o 输出的c++文件
将 oc
代码转成 c++
代码之后,发现内部确实是结构体
面试题来袭!前方请做好准备!!
-
一个NSObject 对象,占用多少内存
-
思路:
-
- 由上面可知,
NSObject
的本质是结构体,通过NSObject.m
可以发现,NSObject
只有一个isa
成员,isa
的本质是class
,struct objc_object *
类型,所以应该占据 8 字节
- 由上面可知,
- 2.
NSLog(@"%zu",class_getInstanceSize([NSObject class]));
输出 - size = 8
-
-
注意!实际上,
{ //获得 - NSObject 一个实例对象的成员变量所占用的大小 >> 8 NSLog(@"%zu",class_getInstanceSize([NSObject class])); NSObject *obj = [[NSObject alloc]init]; // 获取 obj 指针,指向的内存大小 >> 16 NSLog(@"%zu",malloc_size((__bridge const void *)obj)); } 复制代码
//基于 `objc-class.m` 文件 750 版本 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() { return word_align(unalignedInstanceSize()); } 复制代码
对象创建 - alloc init
, 查找alloc底层实现
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } 复制代码
-
CoreFoundation
硬性规定,一个对象,至少有 16 字节 - 以
NSObject
为例,这里传入的size
是alignedInstanceSize
, 而alignedInstanceSize
已经知道 = 8, 8 < 16,retun 16, 最终 NSObject 创建的对象,占据的内存大小是 16
lldb 调试下,使用 memory read
查看对象内存
(lldb) p obj (NSObject *) $0 = 0x000060000000eb90 (lldb) memory read 0x000060000000eb90 0x60000000eb90: a8 6e 3a 0b 01 00 00 00 00 00 00 00 00 00 00 00 复制代码
也能发现,前8 位存储 isa
指针,后 8 位都是0,但是整个对象还是占据了 16 个字节
一个NSObject内存分配示意图
总结:
- 问 :一个NSObject 对象,占用多少内存?
- 答 :
- 系统在alloc的时候,分配了16个字节给
NSObject
对象(malloc_size
函数获得) - 但是实际上
NSObject
只使用了 8个字节的存储空间(64bit系统下) - 可以通过
class_getInstanceSize()
- 系统在alloc的时候,分配了16个字节给
循序渐进之面试题又来了!!
@interface MNStudent : NSObject { int _age; int _no; } @end 复制代码
- 问:一个MNStudent 对象,占用多少内存
- 答:
NSObject
哈哈!中计了!
原理解释:
- 之前
NSObject
创建一个对象,确实是分配了 16 个字节的空间 - 但是,他还有未使用的空间8个字节,还是可以存储的
- 这里的
age
&&no
存进去,正好16
,之前分配的空间正好够用!所以答案是 16!
循循序渐进之面试题双来了!!
(大哥别打了,这次保证不挖坑了,别打,疼,别打脸别打脸。。。)
@interface MNPerson : NSObject { int _age; int _height; NSString *name; } 复制代码
- 问: 一个
MNPerson
对象,占用多少内存 - 答: 这题我真的会了!上面的坑老夫已经知道了!
- 默认创建的时候,分配的内容是16
-
isa
= 8,int age
= 4,int height
= 4,NSString
= char * = 8 - 最终分配: 8 + 4 + 4 + 8 = 24
哈哈哈哈! 又中计了!
这时候你肯定好奇了
uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } 复制代码
-
extraBytes
一般都是 0,这里可以理解为size = alignedInstanceSize()
; -
alignedInstanceSize = class_getInstanceSize
,class_getInstanceSize
由上图的log信息也可以知道 =24
- 内心os: who tm fucking 32?
下载`libmalloc`,找到`calloc`的实现 void * calloc(size_t num_items, size_t size) { void *retval; retval = malloc_zone_calloc(default_zone, num_items, size); if (retval == NULL) { errno = ENOMEM; } return retval; } 发现这个函数,就是我们调用的`calloc`函数的底层 void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) { MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0); void *ptr; if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) { internal_check(); } ptr = zone->calloc(zone, num_items, size); if (malloc_logger) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone, (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0); } MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr); return ptr; } 复制代码
内心os: exo me? 这传入的 size_t size = 24
,怎么返回32的??
涉及到 - 内存对齐
检索 Buckets
- (libmalloc 源码)
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */ 复制代码
MNPerson
补充说明: sizeof
运算符
(lldb) po [obj class] MNPerson (lldb) po sizeof(obj) 8 (lldb) po sizeof(int) 4 复制代码
sizeof obj sizeof
OC对象的分类
- 实例对象(instance对象)
- 类对象(class对象)
- 元类对象(meta-class对象)
instance 对象
- 通过类
alloc
出来的对象 - 每次
alloc
都会产生新的instance
对象(内存不相同) -
instance
对象存储的信息- isa 指针
- 其他成员变量
class 对象
- 是创建对象的蓝图,描述了所创建的对象共同的属性和方法( made in 维基百科 )
- 类在内存中只有一份,每个类在内存中都有且只有一个
class
对象 -
class
对象在内存中存储的信息- isa 指针
- superclass 指针
- 类的对象方法 && 协议
- 类的属性 && 成员变量信息
- 。。。
meta-class
Class metaClass = object_getClass([NSObject class]);
-
metaclss
是NSObject
的meta-class
对象 -
meta-class
在内存中只有一份,每个类都有且只有一个meta-class
对象 -
meta-class
也是类,与class
的对象结构一样,但是内部的数据不一样(用途不同) -
meta-clas
包括:- isa指针
- superclass
- 类方法
- 。。。
提问: object_getClass
与 objc_getClass
的区别
Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } 复制代码
- object_getClass : 传入的是可以是任意对象(id类型),返回的是类 or 元类
- 如果传入
instance
对象,返回class
- 如果传入
class
, 返回的是meta-class
对象 - 如果传入的是
meta-class
,返回的是root-meta-class
对象
- 如果传入
Class objc_getClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); } 复制代码
- 传入的是类名字符串,返回的是该类名对应的类
- 不能返回元类
(图片来自于 www.sealiesoftware.com/blog/archiv… )
看懂这张图 - 就等价于看懂 isa
&& superclass
了
探究流程:
@interface MNSuperclass : NSObject - (void)superclassInstanceMethod; + (void)superClassMethod; @end @implementation MNSuperclass - (void)superclassInstanceMethod{ NSLog(@"superclass-InstanceMethod - %p",self); } + (void)superClassMethod{ NSLog(@"+ superClass-classMethod- %p",self); } @end @interface MNSubclass : MNSuperclass - (void)subclassInstanceMethod; @end @implementation MNSubclass - (void)subclassInstanceMethod{ NSLog(@"subclassInstanceMethod- %p",self); } @end 复制代码
问: 子类调用父类的对象方法,执行的流程是如何的?
MNSubclass *subclass = [[MNSubclass alloc]init]; [subclass superclassInstanceMethod]; 复制代码
- 思路:
-
subclass
调用对象方法,对象方法存在class
中 - 第一步,先找到
subclass
对象,通过isa
指针,找到其对应的MNSubclass
类 - 看
MNSubclass
是否有superclassInstanceMethod
方法的实现,发现没有,MNSubclass
沿着superclass
指针找到他的父类 -MNSuperclass
- 此时,
MNSuperclass
中找到superclassInstanceMethod
的实现,调用它,整个流程结束
-
[MNSubclass superClassMethod];
问: 子类调用父类的类方法,执行的流程是如何的?
- 思路:
- 类方法存在
meta-class
中 - 第一步,找到对应的
MNSubclass
,沿着isa
指针,找到其对应的meta-class
- 看
MNSubclass
的meta-class
中是否有superClassMethod
方法的实现,发现没有,沿着superclass
指针找到MNSuperclass
的meta-class
- 发现
MNSuperclass
的meta-class
有superClassMethod
方法实现,调用,流程结束
- 类方法存在
图中比较难理解的一根线
探究 : 元类对象的superclass 指针是否指向 rootclass
- 分析:
-
meta-class
对象存储的是类方法,class
存储的是 对象方法 - 从面向对象的角度来讲,一个类调用一个类方法,不应该最后调用到 对象方法
- 这里的
Root class
就是NSObject
, 要给NSObject
添加方法就要用到分类
- 验证
NSObject
的对象方法是否会被调用
-
//"NSObject+MNTest"类的声明 && 实现 @interface NSObject (MNTest) + (void)checkSuperclass; @end @implementation NSObject (MNTest) + (void)checkSuperclass{ NSLog(@"+NSObject checkSuperclass - %p",self); } @end //main函数中调用 int main(int argc, char * argv[]) { @autoreleasepool { [MNSubclass checkSuperclass]; NSLog(@"MNSubclass = %p",[MNSubclass class]); } return 0; } -------------------------------------------------------- 控制台输出: +NSObject checkSuperclass - 0x105817040 InterView-obj-isa-class[36303:7016608] MNSubclass = 0x105817040 复制代码
- 发现,调用
checkSuperclass
类方法的,是MNSubclass
类 - 这时候要验证上面那条
meta-class
指向root-class
的线, 这里的root-class
即等于NSObject
-
root-class
中只存对象方法,这里,只要验证,NSObject
中同名的类方法实现取消,变成同名的对象方法测试,即可得出结论 - 声明的还是
+ (void)checkSuperclass
,实现的方法用- (void)checkSuperclass
对象方法替换
@interface NSObject (MNTest) + (void)checkSuperclass; @end @implementation NSObject (MNTest) //+ (void)checkSuperclass{ // NSLog(@"+NSObject checkSuperclass - %p",self); //} - (void)checkSuperclass{ NSLog(@"-NSObject checkSuperclass - %p",self); } @end //main函数中调用 int main(int argc, char * argv[]) { @autoreleasepool { [MNSubclass checkSuperclass]; NSLog(@"MNSubclass = %p",[MNSubclass class]); } return 0; } -------------------------------------------------------- 控制台输出: -NSObject checkSuperclass - 0x101239040 InterView-obj-isa-class[36391:7022301] MNSubclass = 0x101239040 复制代码
发现 - 调用的还是类方法 + (void)checkSuperclass
,但是最终实现的,却是对象方法 - (void)checkSuperclass
- 原因:
-
[MNSubclass checkSuperclass]
其实本质上,调用的是发送消息方法,函数类似是objc_msgsend([MNSubclass class], @selector(checkSuperclass))
- 这里的
@selector(checkSuperclass)
并未说明是 类方法 or 对象方法 - 所以最终走流程图的话,
root-meta-class
通过isa
找到了root-class
(NSObject), -
NSObject
类不是元类,存储的是对象方法,所以 最终调用了NSObject -checkSuperclass
这个对象方法
-
叮叮叮!循循循序渐进之面试题叒来了!!
@implementation MNSubclass - (void)compareSelfWithSuperclass{ NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); } @end 调用: MNSubclass *subclass = [[MNSubclass alloc]init]; [subclass subclassInstanceMethod]; 复制代码
- 问:
[self class]
&&[super class]
分别输出什么
@protocol NSObject - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead"); 复制代码
- 思路:
-
class
方法 是NSObject
的一个对象方法,对方方法存在class
中 - 第一步,先找到
subclass
对象,通过isa
指针,找到其对应的MNSubclass
类 - 看
MNSubclass
是否有class
方法的实现,发现没有,MNSubclass
沿着superclass
指针找到他的父类 -MNSuperclass
- 查询
MNSuperclass
中是否有class
方法的实现,发现没有,MNSuperclass
沿着superclass
指针找到他的父类 -NSObject
- 最终在
NSObject
中找到class
的实现 - 而调用方都是都是当前对象,所以最后输出都是 -
MNSubclass
-
验证:
NSLog(@"self class = %@",[self class]); NSLog(@"super class = %@",[super class]); ---------------------------------------------------------------------- 控制台输出: InterView-obj-isa-class[36796:7048007] self class = MNSubclass InterView-obj-isa-class[36796:7048007] super class = MNSubclass 复制代码
感谢 小码哥 的精彩演出,文章中如果有说错的地方,欢迎提出,一起学习~
欢迎点赞fork~
友情客串:
opensource.apple.com/tarballs/li… opensource.apple.com/tarballs/ob… www.sealiesoftware.com/blog/archiv… )
以上所述就是小编给大家介绍的《面试驱动技术之 - isa && 元类 && 函数调用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Python 函数调用&定义函数&函数参数
- Linux内核如何替换内核函数并调用原始函数
- gdb 如何调用函数?
- 汇编层面分析函数调用
- 理解 Golang 的函数调用
- Wasm 介绍(六):间接函数调用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。