文章目录
  1. 1. Objective-C类和对象的实现原理
    1. 1.1. Class结构
    2. 1.2. 类的继承
    3. 1.3. 总结
  2. 2. Objective—C消息发送与转发
    1. 2.1. 消息
    2. 2.2. 编译转化
    3. 2.3. objc_msgSend
    4. 2.4. 动态方法解析
    5. 2.5. 重定向
    6. 2.6. 消息转发
    7. 2.7. 总结
    8. 2.8. 优化

Objective-C类和对象的实现原理

我是LastDays,我的博客:LastDays.cn

最近一直在了解runtime的实现原理,所以简单的分享一下。

Objective-C的runtime是开源的,但是开始的时候并没有开源NSObject底层的实现,但是后来objc4-532.2发布后,苹果也将NSObject的实现原理加了进来

在这里可以下载Runtime的源码:objc4

Class结构

runtime中NSObject.mm中的一段代码:

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
@protocol NSObject

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

- (Class)superclass;
- (Class)class;
- (id)self;
- (NSZone *)zone;

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (BOOL)isProxy;

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (BOOL)respondsToSelector:(SEL)aSelector;

- (id)retain;
- (oneway void)release;
- (id)autorelease;
- (NSUInteger)retainCount;

- (NSString *)description;
- (NSString *)debugDescription;

@end

OBJC_EXPORT
@interface NSObject <NSObject>
{
Class isa;
}
@end

这段代码中

1
2
3
4
5
@interface NSObject <NSObject>
{
Class isa;
}
@end

引起了我的关注,接着发现runtime中声明了id和Class两种类型:

1
2
3
4
5
6
7
8
9
10
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

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

/// A pointer to an instance of a class.
typedef struct objc_object *id;

在runtime的objc.h中

1
2
3
4
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;

其实,Objective-C中的对象就是一个结构体,并且所有的对象都有一个相同的结构体。其实,Class就是类,id就是对象。而且我们可以看到每一个对象都有一个isa指针,这是这个isa就是指向该类。

看一下objc_class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class {
Class isa;

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

进行一下简单的描述(如果不对希望大家能够指出)

  • super_class 父类
  • name 类名
  • version 类版本信息(默认为0)
  • info 类信息
  • instance_size 实例变量大小
  • ivars 成员变量
  • methodLists 方法
  • cache 方法缓存
  • protocols 协议

在这里我们同样也看到了一个isa指针,他被称为元类。这里Class其实看起来就像是一个对象,需要有地方去存储它的方法列表,类名等信息。那么元类就是Class的isa指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface LastDays : NSObject

@property NSString *name;

-(void)showName1;

+(void)showName2;

@end


@implementation LastDays

-(void)showName1{
NSLog(@"类");
}

+(void)showName2{
NSLog(@"元类");
}

@end

所有LastDays的实例化对象的isa都指向LastDays(类),LastDays中记录着父类类名,类版本信息(默认为0),类信息,实例变量大小,成员变量,方法,方法缓存,协议等信息,那么LastDays中的isa指向了元类,元类中只记录着类名,类方法列表等,所有的元类指向一个根元类,根元类的isa指向自己,这样就实现了一个封闭的内循环

举例说明:

1
2
3
4
5
Lastdays *ly = [[LastDays] alloc] init]; //初始化ly

[ly showName1];//发送showName1消息时,ly变量中的isa会遍历LastDays中的类方法列表找到showName1

[LastDays showName2];//发送showName2时,LastDays的isa指针会遍历LastDays的元类类方法

类的继承

关于类的继承,其实我们可以根据类的实现可以推断出来。

objc_class结构中我们可以发现有一个super_class指针,其实这个指针是指向我们的父类。

我们设计这么一种继承结构: LastDays:Last:NSObject

如图所示就是继承实现的原理:

通过以上图形,根据蓝线我们可以看出,每个对象的isa都不为空,这样只要是有一个id类型的对象,通过runtime机制都可以获取该对象。

根据黑线我们可以看出,最终继承自NSObject,并且NSObject的superclass为nil。

同样也有很多公开的函数可以访问类信息

例如:输出类方法

1
2
3
4
5
6
7
8
9
10
11
void PrintObjectMethods() {
unsigned int count = 0;
Method *methods = class_copyMethodList(self,
&count);
for (unsigned int i = 0; i < count; ++i) {
SEL sel = method_getName(methods[i]);
const char *name = sel_getName(sel);
printf("%s\n", name);
}
free(methods);
}

总结

以上就是我对Objective-C类和对象的实现原理的理解,有什么问题或者错误欢迎大家在评论处指出。

Objective—C消息发送与转发

我是LastDays一个热爱分享交流的,一个喜欢白菜的90后,LastDays

消息

上一篇文章Objective-C类和对象的实现原理对Objective-C类和对象的实现原理的实现原理进行了总结。我在最开始学习的时候,一直认为[receiver message]仅仅就是一个方法调用,但是后来深入学习以后才明白这是”发送消息”

编译转化

在[receiver message]的执行过程当中,[receiver message]是会被动态编译的,Objc是动态语言,因此它会想尽办法将编译连接推迟到运行时来做。runtime这个时实运行系统就是来执行编译后的代码,编译成什么样runtime才能认识呢?

这里有段代码

1
#import <Foundation/Foundation.h>
#import "Receiver.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
            
        Receiver *receiver = [[Receiver alloc] init];
        [receiver message];
    }
    return 0;
}

我们使用clang将main.m转换为c代码来看下结构

我们终端下输入:

1
clang -rewrite-objc main.m

在转换后代码查找到这个:

1
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        Receiver *receiver = ((Receiver *(*)(id, SEL))(void *)objc_msgSend)((id)((Receiver *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Receiver"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));
    }
    return 0;
}

来看下这段代码:

1
((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));

所以说objc发消息后大多数都是objc_msgSend函数的调用

根据苹果文档对Runtime的解释

1
id objc_msgSend(id self, SEL _cmd, ...)

将一个消息发送给一个对象,并且返回一个值。

  • self是消息的接受者,
  • _cmd是selector,
  • …是可变参数列表。

对象发送消息,调用objc_msgSend,super发送消息时,调用的是objc_msgSendSupe,如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

objc_msgSend

经过了上面的铺垫,分析下objc_msgSend的发送过程:

objc_msgSend的动作其实是比较清晰的,上面我们提到过,runtime这个实时运行系统就是用来运行编译后的代码。

运行时的数据结构在Objective-C类和对象的实现原理中提到过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
Class isa;
};
struct objc_class {
Class isa;
}

/// 不透明结构体, selector
typedef struct objc_selector *SEL;

/// 函数指针, 用于表示对象方法的实现
typedef id (*IMP)(id, SEL, ...);
  • id:指代objc中的对象,我们不能也定它的结构,但是首地址指向isa指针。通过isa指针,运行时就能拿到objc_class。
  • objc_class: 表示对象的Class,它的结构是确定的,由编译器生成。
  • SEL:表示选择器,这是一个不透明结构体。但是实际上,通常可以把它理解为一个字符串。例如printf(“%s”,@selector(isEqual:))会打印出”isEqual:”。运行时维护着一张SEL的表,将相同字符串的方法名映射到唯一一个SEL。 通过sel_registerName(char *name)方法,可以查找到这张表中方法名对应的SEL。苹果提供了一个语法糖@selector用来方便地调用该函数。
  • IMP:是一个函数指针。objc中的方法最终会被转换成纯C的函数,IMP就是为了表示这些函数的地址。

objc_msgSend的执行过程:

(1)检查接收的对象是否为nil,如果是,调用nil处理方案

(2)在objc_object结构体中含有cache,首先会在Class的cache中查找IMP(如果没有缓存则会初始化缓存),如果找到就会跳转到对应的函数上执行。

(3)如果没有找到就像父类的Class查找,如果还没有没找到就继续向上查找,知道找到根类。

(4)如果找到根类还是没有实现方案,这个时候就需要使用_objc_msgForward函数指针替代imp,最后来执行这个imp(动态方法实现)。

也就是顺着这个结构进行查找。

其实编译器会根据情况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。排列组合正好四个方法。

动态方法解析

在进入消息转发之前,动态方法解析会在转发机制之前执行。

调用resolveInstanceMethod(或者resolveClassMethod):如果runtime在cache或者方法列表中没有找到目标实现方法,这个时候重载resolveInstanceMethod(或者resolveClassMethod)返回YES,可以动态添加方法。我们使用class_addMethod就可以向特定的类添加特定方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end

当然如果想进入转发机制,可以让resolveInstanceMethod返回NO。

重定向

在进入消息转发之前,runtime总是仁慈的提供给我们很多机会

调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。

也就是说在进入消息转发机制之前我们可以通过重载forwardingTargetForSelector方法替换消息的接受者为其他对象,切记不能返回self,这样会进入死循环状态。如果返回了nil,继续下面的动作。

1
2
3
4
5
6
7
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}

消息转发

(1)调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常

(2)调用forwardInvocation:方法,将地1步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

这就是消息转发,当调用forwardInvocation的时候,我们同样可以重载forwardInvocation,来定义我们的转发逻辑:

1
2
3
4
- (void)forwardInvocation:(NSInvocation *)anInvocation
{

}

总结

resolveInstanceMethodforwardingTargetForSelectormethodSignatureForSelectorforwardInvocation,这四个方法在开发的过程当中都可以进行override,使用runtime来调用,经常使用的可能是实现消息转发,就是重写方法methodSignatureForSelectorforwardInvocation,吞掉一个消息或者代理给其他对象都是没问题的。

整个过程也可以用这个图表示:

优化

其实一个objc程序在启动的时候,需要一个准备时间,这个准备时间就是对cache的初始化,之后所有的消息发送都是先访问cache,这样的设计可以提高效率。

文章目录
  1. 1. Objective-C类和对象的实现原理
    1. 1.1. Class结构
    2. 1.2. 类的继承
    3. 1.3. 总结
  2. 2. Objective—C消息发送与转发
    1. 2.1. 消息
    2. 2.2. 编译转化
    3. 2.3. objc_msgSend
    4. 2.4. 动态方法解析
    5. 2.5. 重定向
    6. 2.6. 消息转发
    7. 2.7. 总结
    8. 2.8. 优化