OC及其动态性
Objective-C是基于C封装的一门面向对象的语言,底层实现是通过C/C++代码实现的。OC语言最大的特点就是其动态性,它会尽可能地把决策从编译时和连接时推迟到运行时(简单来说,就是编译后的文件不全是机器指令,还有一部分中间代码,在运行的时候,通过Runtime再把需要转换的中间代码再翻译成机器指令)。
Runtime与消息机制
Runtime是OC的一套由C和汇编编写的库(一些调用频率较高的方法是由汇编编写的),它是OC具有动态的的最主要条件。当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。因此OC方法在运行时,都是作为消息在传递的,我们甚至可以把方法叫做消息,甚至可以说OC就是一门消息语言。
OC对象
在我们讲消息机制前首先要了解OC的对象,才能了解对象的方法调用过程。
OC的的底层其实就结构体
,长这个样子。
这个就是结构体的内部;
由上而下,objc_class
这个结构体就是一个对象,它由三部分组成,
- isa (指向对象父类的指针)
- superclass(它的父类)
- cache_t(对象的调用过的方法列表)
- bits(对象的更多信息)
class_rw_t
是将bit通过位运算的结果取其[3, 47]位,转换而成。这里包含了类的方法方法列表,属性列表及协议列表等。ro
就是rootclass的意思他其中包含了类的成员变量等。
OC类的继承体系
NSString *str = [NSString string]
str
是一个事例对象,它内部的isa指针指向它的类NSString
,
NSString
也是一个OC类,它内部也有isa,isa指向它的元类对象NSString meta-class
(NSString的元类对象是NSString)
NSString meta-class````也是个OC类,它的isa指向它的元类
meta-class。
meta-class也是一个对象,它的isa指向哪里?为了防止它无限延伸下去,设计出了
meta-class指向基类的
meta-class以此作为它们的所属类。即,任何NSObject继承体系下的
meta-class都使用
NSObject的
meta-class```作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
事例如下图。
消息机制
已经介绍类OC对象,现在可以runtime消息机制的主题了。我们的调用类方法也好,对象方法也好,都会被转成 objc_msgSend
的消息。由于OC的底层是由C和C++实现的,我们就在OC文件目录下把它转成C++文件。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc +要转的OC文件
例子:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//定义一个Dog类,它有两个方法一个是eat对象方法,另一个是eat类方法。
Dog *wangCai = [[Dog alloc]init];
[wangCai eat];
[Dog eat];
// (objc_msgSend)(wangCai, sel_registerName("eat")); //对象方法
// (objc_msgSend)(objc_getClass("Dog"), sel_registerName("eat")); //类方法
这是的Dog类的.h文件
@interface Dog : NSObject
- (void)eat;
- (void)bark;
+ (void)play;
@end
我们注释掉它的eat方法实现,
#import "Dog.h"
#import
#import "Cat.h"
@implementation Dog
//- (void)eat{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
@end
消息机制--动态方法解析
现在调用eat方法它会出错,其实OC在找不到方法实现的时候,它会动态调用runtime的这个方法+ (BOOL)resolveInstanceMethod:(SEL)sel
#import "Dog.h"
#import
#import "Cat.h"
@implementation Dog
//- (void)eat
//{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
/*
2.0动态方法解析
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(bark)); //调用Dog类的bark方法, 打印输出的结果是dog--bark
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
这样我们就实现了动态给OC对象寻找实现,防止崩溃的方法
消息机制--消息转发
如果我们不实现resolveInstanceMethod
程序必然会崩溃吗?别急runtime还有第二个机制防止奔溃- (id)forwardingTargetForSelector:(SEL)aSelector
消息转发机制,你不是处理不了吗?那你吧消息转给别人,让有能力的类处理。
我们定义一个处理这eat方法的Cat类,.h的声明写不写都成,因为它会直接在方法实现中搜取
#import "Cat.h"
@implementation Cat
- (void)eat{
NSLog(@"Cat--eat");
}
@end
我们在Dog的类中需要做如下处理,把消息转发给Cat让Cat帮它去处理
///Dog.m类
/*
3.0 消息转发
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [[Cat alloc] init]; //返回空否
}
return [super forwardingTargetForSelector:aSelector];
}
//这样处理Cat的eat类会被调用,打印出Cat--eat
当然消息转发的时候也不知道转给谁(即- (id)forwardingTargetForSelector:(SEL)aSelector
返回的是空对象nil),可以在- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法里自己生成个签名,然后实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
方法,收集日志防止程序崩溃
///Dog.m类
/*
3.0 消息转发
*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
/*
3.1消息转发
*/
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [NSMethodSignature signatureWithObjCTypes:"@@:*"];//手动创建一个方法签名
}
return [super methodSignatureForSelector:aSelector];
}
/*
3.1.1消息转发
*/
//自定义的方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"调用的方法找不到实现");
}
如果你这会儿知道谁能处理这个消息,也可以这样处理,
#import "Dog.h"
#import
#import "Cat.h"
@interface Dog ()
@property (nonatomic,strong) Cat *miCat;
@end
@implementation Dog
//- (void)eat{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
/*
3.0 消息转发
*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
/*
3.1消息转发
*/
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(eat)) {
self.miCat = [Cat new];
return [self.miCat methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
/*
3.1.1消息转发
*/
//自定义的方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// NSLog(@"调用的方法找不到实现");
if (anInvocation.selector == @selector(eat)) {
[anInvocation invokeWithTarget:self.miCat];
}
}
@end
/// Cat类的eat方法同样会被调用,打印出Cat--eat
从上面可以看出runtime的消息机制分为三步:
- 消息发送
- 动态方法解析
- 消息转发
只有这三层保护全部没有才会报错。