Objective-c对象&类&元类&内存对齐等的知识点

原创文章
声明:作者声明此文章为原创,未经作者同意,请勿转载,若转载,务必注明本站出处,本平台保留追究侵权法律责任的权利。
全栈老韩
全栈工程师,擅长iOS App开发、前端(vue、react、nuxt、小程序&Taro)开发、Flutter、React Native、后端(midwayjs、golang、express、koa)开发、docker容器、seo优化等。

一、概览

1.1 类的本质

是结构体。

objc2.0源码之前,使用的是objc_class结构体:
老objc_class.png
objc2.0源码新使用的objc_class结构体:
新objc_class.png

  • clang
    将代码编译成c++的代码使用clang:
clang 复制代码
clang -rewrite-obj main.m -o main.cpp
  • xcrun
    使用xcrun指定sdk进行输出c++代码
xcrun 复制代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx源文件 -o 输出的c++文件名

1.2 获取对象的内存大小

在runtime的<objc/runtime.h>中,定一个了一个class_getInstanceSize的函数,可以获取类的实例的大小:
class_getInstanceSize.jpg

class_getInstanceSize使用示例.jpg

从图中可以看出,系统给对象分配了16个字节,但是对象只占了8个字节。

  • class_getInstanceSize获取的是内存对齐后的成员变量相加后所占用的大小
  • malloc_size获取的是系统给对象开辟内存的大小

oc的runtime源码中要求所有的对象开辟空间至少是16字节。

oc->instanceSize.jpg

1.3 实例对象探究

结构体的成员在内存中是连续存储的。
对象内存结构.jpg
如图所示,示例对象中存储了isa指针和成员变量的值。

1.4 内存对齐

oc中规定对象最小开辟空间是16个字节。

即使没有这个约束,那么还有一个内存对齐。

内存对齐:结构体的大小必须是最大成员大小的倍数。
内存对齐.jpg

针对stu,其实是这样理解:isa是8个字节,int占4个字节,加起来是12,但是由于内存对齐规则,所以需要是8的倍数,那么接近12的8的倍数,就是16。

iOS系统的要求是,malloc分配出来的内存空间,一定是16(buket size的最大值是256)的倍数。

1.5 对象实例中的存储

实例中存储isa指针和成员变量的值,没有方法。

1.6 sizeof

会在编译器确定传入的值是什么类型,比如,如果是指针类型,直接替换成8,如果是int类型,那么替换成4.

二、示例解读

2.1 如下示例

内存大小探究.jpg

这个示例中,结构体的class_getInstanceSize大小是24,但是malloc_size是32。
内存大小探究2.jpg

解释:iOS系统的要求是,malloc分配出来的内存空间,一定是16(buket size的最大值是256)的倍数。instanceSize只需要24个字节就可以存储,但是分配内存时又要是16的倍数。

三、类对象

3.1 获取类对象

oc.m 复制代码
NSObject *obj = [[NSOject alloc] init];

Class objCls = [obj class];

Class objCls1 = [NSObject class];

Class objCls2 = object_getClass(obj);

每个类在内存中有且只有一个class对象。

3.2 类对象中存储的信息

  • isa指针
  • superclass指针
  • 类的属性信息、类的对象方法信息
  • 类的协议信息、类的成员变量信息

四、元类对象

4.1 获取元类对象

oc.m 复制代码
Class metaCls = object_getClass([NSObject class]);

Class metaCls1 = object_getClass([obj class]);

每个类在内存中有且只有一个meta-class对象。

4.2 元类中存储的数据

  • isa指针
  • superclass指针
  • 类的类方法信息

4.3 判断是否是元类对象

oc.m 复制代码
class_isMetaClass([NSObject class]);

4.4 Objective-c中为什么设计元类

主要是为了复用消息机制。参考下面五中的指向图。

五、isa & superclass

实例对象、类对象、元类对象都有isa指针,有一个指向图如下:
isa指向图.jpg

isa和superclass的指向都如图所示。

5.1 经典案例

oc.m 复制代码
// 新增一个NSObject+Test分类
@interface NSObject (Test)

+ (void)test;

@end

@implementation NSObject (Test)

+ (void)test {
    NSLog(@"NSObject中的+test", self);
}

- (void)test {
    NSLog(@"NSObject中的-test", self);
}

@end


// 一个oc类Person
@interface Person:  NSObject

+ (void)test;

@end

@implementation Person

+ (void)test {
    NSLog(@"Person中的+test", self);
}

@end


// main.m中的main函数
int main(int argc, const char *argv[]) {
    NSLog(@"Person中的类adr: %p", [Person class]);
    NSLog(@"NSObject中的类adr: %p", [NSObject class]);
    
    [Person test];
    [NSObject test];
}
  • 第一种情况:NSObject分类Test中声明了类方法test以及实现,Person类也声明和实现了类方法。

会各自调用自己的类方法,打印各自类方法中的NSLog;

打印的self,也是各自的类对象地址。各自的self都是各自类对象地址。

  • 第二种情况:NSObject分类Test中声明了类方法test以及实现,Person类只声明,但不实现类方法。

NSObject分类会调用自己的类方法,打印自几类方法中的NSLog;

Person调用NSObject中的类方法test。

输出的self也是各自的类对象地址。

  • 第三种情况:NSObject分类Test中声明了类方法+test,但只实现实例方法-test,Person类只声明+test,但不实现类方法。

输出的self也是各自的类对象地址。因为调用+test方法的接收者都是各自的类对象。

NSObject分类和Person都会调用分类的实例方法。

因为当NSObject元类中没有类+test方法时,会从superclass找到父类NSObject,父类NSObject中有方法名test【是不会辨别类方法和实例方法的,只关注方法名】,所以尽管main函数中是调用了类方法,但是最终会走到NSObject的相同方法名的实例方法中去。

5.2 示例[self class]和[super class]

oc.m 复制代码
@interface Person: NSObject
@end

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"self class: %@;super class: %@", [self class], [super class])
    }
    return self;
}

@end

以上代码均打印Person对象地址。

解释:
[self class],对象的class,直接就是对象的isa指向,所以是Person类。

[super class],表示从当前类的父类进行查找方法,所以以上代码需要从Person的父类NSObject中去找,NSObject实现了class方法,那么读取方法接收者的isa指针,这里的方法接收者还是self,所以self的isa指针指向的还是Person类。

使用clang将oc转为c++:
(下面这张图是偷懒从网上找的)
oc转c++.jpg

按照苹果的文档,super是一个指向objc_super结构体的指针。
objc_super.jpg

[super class]的消息接收者即receiver是self。所以当最后找isa指针时,还是找的self的isa指向的类Person。

5.3 isa & isa_mask才能是类的地址值

isa_mask逻辑.jgp

isa是一个共同体,如下:
isa结构体.png

示例(控制台中通过lldb调试,验证class的isa是需要&isa_mask的值,就是元类的地址):
isa_mask的lldb调试.jpg

上图中,因为objc2.0,将isa设置为private,所以在oc中使用lldb调试时,使用personClass->isa是拿不到isa的,所以上图中的mj_objc_class这个结构体模拟的就是objc2.0中object_class这个结构体,目的是为了给oc暴露isa这个指针来查看isa指向的地址值
objc_object中的private私有isa.png

superclass没有使用mask,所以是直接指向的父类的地址。
superclass的lldb调试.jpg

六、objc_class的探索

新的objc源码中是使用了objc_class,继承自objc_object【c++语法】。
objc_class源码.jpg

6.1 objc_class的数据结构

objc_class结构.jpg

6.2 小结

ios面试题.jpg

七、分类

7.1 分类的方法和类方法

  • 分类的方法是和主类的方法是存在一起的,都在元类的method_list_t
  • 分类的方法不会覆盖主类的方法
  • 分类如果有和类同名的方法,分类的方法排在主类的前面
  • 分类的加载在主类之后
    如果要确保只使用主类中的方法:
    响应本类方法.jpg

文章太长,其他部分下篇再附上。

暂无评论,快来发表第一条评论吧