Objective-C对象模型

在OC中,每一个对象都有一个名为 isa 的指针,指向该对象的类。这个类描述了一系列它的实例相关内容,包括成员变量的列表,成员函数的列表等。每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。

Objective-C中对象的定义

1
2
3
4
5
6
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

通过看NSObject.h文件可以知道,基本上包括Class isa指针的都可以被称为对象

经典OC对象模型图

Objective-C中对象的初始化

  • 如何初始化

    Class *obj = [[Class alloc] init]
    alloc申请下一块内存给对象(alloc不只分配在虚拟内存,同时会在物理内存建立映射)
    init初始化相关的信息

1
2
Class *obj = [Class alloc];
obj = [obj init];
1
2
3
4
5
6
7
- (instancetype)init {
self = [super init];
if(self) {
//此次完成相关自定义的初始化信息
}
return self;
}

OC对象在内存中的结构

OC对象在内存中的三个部分

  1. isa结构体指针

    • 类型信息
    • 函数指针
  2. 父类的部分

    • 父类的成员变量
  3. 自己类的部分

    • 自己类的成员变量

C++对象(对比)一样的分为三部分

  1. 虚函数表
  2. 父类部分
  3. 自己的部分

ps:

  • SEL: SEL对象,SEL对象包含方法的编号,放在SEL表中;
  • IMP: 保存了方法地址的函数指针,放在IMP表中;
  • SEL通过对应关系,在IMP表中找到相应的函数指针.

OC对象结构分析

首先,在OC中大部分事物都是对象,包括类也是一种对象。就像我们可以调用[NSObject alloc]一样,这其实就是向类(对象)发送alloc消息。
既然类也是对象,所以类也有自己的isa指针,指向的则是元类(metaclass)。
同时,元类也是对象,元类的isa指针则指向的是根元类(root metaclass)。
而根元类(root metaclass)的isa指针则指向本身。
到此,OC的对象结果完成了一次闭环

实例对象,类,父类,元类 的关系图 :

关系图

由图可知,对象的类定义了对象的方法,类的元类定义了类的类方法。每个对象都有自己的类,每个类都有自己的元类。
并且,因为类具有继承关系的同时,父类的类方法子类也可以调用,而类方法是在类的元类中定义的。所以实际上 类的元类 和 父类的元类 也是继承关系的。
OC中的对象都是继承自NSObject,而NSObject的类方法 alloc 方法则是又NSObject的元类所定义的。但是NSObject的子类都是可以使用类方法 alloc 的,所以NSObject的子类的元类也必然是继承自NSObject的元类的。

备注:元(meta)的概念来着”元小说”,本质上是讲读者从作者构造好的”盒子”(文章)中脱离出来,在文章中谈论文章本身(比如在文章中谈论这篇文章是怎么写的)。在游戏中类似的作品为《史丹尼的预言》, 在现在,"元" 的概念通常表示本质和根源

OC中类的成员变量

成员变量以图下的方式排列在对象中。

图例:

出于验证,我创建了三个类, grandfather(继承自NSObject) , father(继承自grandfather) , child(继承自father).
并分别添加各种的变量。
然后实例化child后,打印child:

1
2
3
4
5
6
7
(chid) $0 = {
father = {
grandfather = (_grandfather_mv = 0)
_father_mv = 0
}
_chid_mv = 0
}

其中_<类名>_mv是我自定义的变量名。
可以发现其结构与图例基本一致。但是没有打印出NSObject层级的isa指针。但是我单独打印NSObject时是有的,可能是xcode做过什么处理吧。


Objective-C对象模型的应用

动态创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)start {  

//创建一个类
Class newClass = objc_allocateClassPair([chid class], "newTestClass", 0);
//给这个类添加一个方法
class_addMethod(newClass, @selector(addtestM), (IMP)addm, "v@:");
//注册这个类,使其能使用
objc_registerClassPair(newClass);

id test = [[newClass alloc] init];
调用这个方法
[test performSelector:@selector(addtestM)];
}

object_getClass和[<对象> class] 的区别,
两者在对象都是实例对象的时候是没有区别的,
但是在对象是类的时候,object_getClass是获取元类的地址,而[<对象> class]仍然是获取当前的类,
根据查找的一些blog提供的内容,object_getClass实际上就是获取的isa指针的内容,但是isa是可被修改的,所以object_getClass获取class是不安全的做法

为了测试之前关于元类的内容,输出一下类和元类的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void addm(id self, SEL _cmd) {
NSLog(@"对象:%@",self);
NSLog(@"类:%@(%p) || 父类:%@(%p)",[self class], [self class], [self superclass], [self superclass]);

Class curClass = [self class];
for (int i = 1; i < 5; ++i) {
NSLog(@"当前isa指针指向的是:%p",curClass);
curClass =object_getClass(curClass);
}

Class objectClass = [NSObject class];
NSLog(@"NSObject地址:%p",objectClass);
NSLog(@"NSObject元类地址:%p",object_getClass(objectClass));
}

结果:

1
2
3
4
5
6
7
8
9
test Start!
对象:<newTestClass: 0x100618010>
类:newTestClass(0x100617e20) || 父类:chid(0x100003ca8)
当前isa指针指向的是:0x100617e20
当前isa指针指向的是:0x100617e50
当前isa指针指向的是:0x7fff9f7310f0
当前isa指针指向的是:0x7fff9f7310f0
NSObject地址:0x7fff9f731140
NSObject元类地址:0x7fff9f7310f0

以此,我们可以看出,对象的类的元类,最后都根元类,
其isa指针都是指向的NSObject的元类指针。
同时NSObject的元类的根元类指向的是它自己

KVO

kvo是一种动态的修改isa的技术。
具体参考runtime.

isa swizzling

isa swizzling是一种动态的替换类方法和实例方法的功能。

原理上则是修改 IMP和SEL 的关系对应表。

官方提供了三种实现方式:

  • method_exchangeImplementations(两个方法交换)
  • class_replaceMethod(会先检查一次新方法是否存在,不存在的话会创建一个方法并交换)
  • method_setImplementation(为一个SEL设置IMP)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#import "methodSwizzlingTest.h"
#import <objc/message.h>

@implementation methodSwizzlingTest

- (void)start {
[self sel1];
[self sel2];
[self sel3];

[self exchangeMethod];

[self sel1];
[self sel2];
[self sel3];
}

#pragma mark - 原方法

- (void)sel1 {
NSLog(@"sel1");
}

- (void)sel2 {
NSLog(@"sel2");
}

- (void)sel3 {
NSLog(@"sel3");
}

#pragma mark - 替换方法

- (void)imp1 {
NSLog(@"imp1 替换了 sel1");
}

- (void)imp2 {
NSLog(@"imp2 替换了 sel2");
}

- (void)imp3 {
NSLog(@"imp3 替换了 sel3");
}

#pragma mark - 交换功能

- (void)exchangeMethod {
static dispatch_once_t onceToken;
Class curclass = [self class];
dispatch_once(&onceToken, ^{
//method_exchangeImplementations
method_exchangeImplementations(
class_getInstanceMethod(curclass, @selector(sel1)),
class_getInstanceMethod(curclass, @selector(imp1)));
//class_replaceMethod
class_replaceMethod(curclass, @selector(sel2), [curclass instanceMethodForSelector:@selector(imp2)], "@:");
//method_setImplementation
method_setImplementation(class_getInstanceMethod(curclass, @selector(sel3)), [curclass instanceMethodForSelector:@selector(imp3)]);
});
}

@end

参考文献:
《iOS开发进阶》(唐巧)
isa指针详解
元类是什么

iOS-学习整理基础篇 Objective-C 与 iOS 编程规范

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×