面试驱动技术之 - isa && 元类 && 函数调用

栏目: IOS · 发布时间: 5年前

内容简介:我们平时编写的Objetcive-C,底层实现都是C/C++实现的以使用指令
面试驱动技术之 - isa && 元类 && 函数调用

面试驱动技术之 - 带着面试题来找答案

  • 一个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++实现的

面试驱动技术之 - isa && 元类 && 函数调用
  • 问 : Objetcive-C 基于 C/C++ 实现的话,Objetcive-C 对象相当于C/C++ 中的什么数据结构呢?
@interface MNPerson : NSObject
{
    int _age;
    double _height;
    NSString *name;
}
复制代码

MNPerson 为例,里面的成员变量有不同类型是,比如 intdoubleNSString 类型,假如在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 对象,占用多少内存

  • 思路:

      1. 由上面可知, 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());
}
复制代码

opensource 源码

对象创建 - 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 为例,这里传入的 sizealignedInstanceSize , 而 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内存分配示意图

面试驱动技术之 - isa && 元类 && 函数调用

总结:

  • 问 :一个NSObject 对象,占用多少内存?
  • 答 :
    • 系统在alloc的时候,分配了16个字节给 NSObject 对象( malloc_size 函数获得)
    • 但是实际上 NSObject 只使用了 8个字节的存储空间(64bit系统下)
    • 可以通过 class_getInstanceSize()

循序渐进之面试题又来了!!

@interface MNStudent : NSObject
{
    int _age;
    int _no;
}
@end
复制代码
  • 问:一个MNStudent 对象,占用多少内存
  • 答:
    NSObject
    
面试驱动技术之 - isa && 元类 && 函数调用

哈哈!中计了!

面试驱动技术之 - isa && 元类 && 函数调用

原理解释:

面试驱动技术之 - isa && 元类 && 函数调用
  1. 之前 NSObject 创建一个对象,确实是分配了 16 个字节的空间
  2. 但是,他还有未使用的空间8个字节,还是可以存储的
  3. 这里的 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
面试驱动技术之 - isa && 元类 && 函数调用

哈哈哈哈! 又中计了!

面试驱动技术之 - isa && 元类 && 函数调用

这时候你肯定好奇了

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?
面试驱动技术之 - isa && 元类 && 函数调用

ios内存分配源码

下载`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
面试驱动技术之 - isa && 元类 && 函数调用

补充说明: 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]);

  • metaclssNSObjectmeta-class 对象
  • meta-class 在内存中只有一份,每个类都有且只有一个 meta-class 对象
  • meta-class 也是类,与 class 的对象结构一样,但是内部的数据不一样(用途不同)
  • meta-clas 包括:
    • isa指针
    • superclass
    • 类方法
    • 。。。
面试驱动技术之 - isa && 元类 && 函数调用

提问: object_getClassobjc_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);
}
复制代码
  • 传入的是类名字符串,返回的是该类名对应的类
  • 不能返回元类
面试驱动技术之 - isa && 元类 && 函数调用

(图片来自于 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 的实现,调用它,整个流程结束
面试驱动技术之 - isa && 元类 && 函数调用

[MNSubclass superClassMethod];

问: 子类调用父类的类方法,执行的流程是如何的?

  • 思路:
    • 类方法存在 meta-class
    • 第一步,找到对应的 MNSubclass ,沿着 isa 指针,找到其对应的 meta-class
    • MNSubclassmeta-class 中是否有 superClassMethod 方法的实现,发现没有,沿着 superclass 指针找到 MNSuperclassmeta-class
    • 发现 MNSuperclassmeta-classsuperClassMethod 方法实现,调用,流程结束
面试驱动技术之 - isa && 元类 && 函数调用

图中比较难理解的一根线

面试驱动技术之 - isa && 元类 && 函数调用

探究 : 元类对象的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

复制代码
  1. 发现,调用 checkSuperclass 类方法的,是 MNSubclass
  2. 这时候要验证上面那条 meta-class 指向 root-class 的线, 这里的 root-class 即等于 NSObject
  3. root-class 中只存对象方法,这里,只要验证, NSObject 中同名的类方法实现取消,变成同名的对象方法测试,即可得出结论
  4. 声明的还是 + (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 这个对象方法
面试驱动技术之 - isa && 元类 && 函数调用

叮叮叮!循循循序渐进之面试题叒来了!!

@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
复制代码

感谢 小码哥 的精彩演出,文章中如果有说错的地方,欢迎提出,一起学习~

demo

欢迎点赞fork~

友情客串:

小码哥

神经病院runtime入学考试

gun

opensource.apple.com/tarballs/li… opensource.apple.com/tarballs/ob… www.sealiesoftware.com/blog/archiv… )


以上所述就是小编给大家介绍的《面试驱动技术之 - isa && 元类 && 函数调用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

从Paxos到Zookeeper

从Paxos到Zookeeper

倪超 / 电子工业出版社 / 2015-2-1 / 75.00元

《Paxos到Zookeeper:分布式一致性原理与实践》从分布式一致性的理论出发,向读者简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维Zoo......一起来看看 《从Paxos到Zookeeper》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换