面试驱动技术之 - 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 && 元类 && 函数调用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Think Python

Think Python

Allen B. Downey / O'Reilly Media / 2012-8-23 / GBP 29.99

Think Python is an introduction to Python programming for students with no programming experience. It starts with the most basic concepts of programming, and is carefully designed to define all terms ......一起来看看 《Think Python》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试