runtime01-消息机制

OC及其动态性

Objective-C是基于C封装的一门面向对象的语言,底层实现是通过C/C++代码实现的。OC语言最大的特点就是其动态性,它会尽可能地把决策从编译时和连接时推迟到运行时(简单来说,就是编译后的文件不全是机器指令,还有一部分中间代码,在运行的时候,通过Runtime再把需要转换的中间代码再翻译成机器指令)。

Runtime与消息机制

Runtime是OC的一套由C和汇编编写的库(一些调用频率较高的方法是由汇编编写的),它是OC具有动态的的最主要条件。当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。因此OC方法在运行时,都是作为消息在传递的,我们甚至可以把方法叫做消息,甚至可以说OC就是一门消息语言。

OC对象

在我们讲消息机制前首先要了解OC的对象,才能了解对象的方法调用过程。

OC的的底层其实就结构体,长这个样子。

1857051-7b4ae80feb9229ce.png
OC对象.png

这个就是结构体的内部;
由上而下,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-classmeta-class也是一个对象,它的isa指向哪里?为了防止它无限延伸下去,设计出了meta-class指向基类的meta-class以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObjectmeta-class```作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
事例如下图。

1857051-5c65b5ee80529af7.png
实例对象、类、元类关系

1857051-dda598928c70f58a.png
继承体系

消息机制

已经介绍类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 <objc/runtime.h>
#import "Cat.h"

@implementation Dog

//- (void)eat{
//    NSLog(@"dog--eat");
//}
- (void)bark{
    NSLog(@"dog--bark");
}
+ (void)play{
    NSLog(@"classfunc-dog--play");
}
@end
1857051-892007dfdeaebdc0.png
消息发送

消息机制--动态方法解析

现在调用eat方法它会出错,其实OC在找不到方法实现的时候,它会动态调用runtime的这个方法+ (BOOL)resolveInstanceMethod:(SEL)sel

#import "Dog.h"
#import <objc/runtime.h>
#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对象寻找实现,防止崩溃的方法


1857051-65abf442de7e86ae.png
动态方法解析

消息机制--消息转发

如果我们不实现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 <objc/runtime.h>
#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
1857051-29c35ddea7482ee6.png
消息转发

从上面可以看出runtime的消息机制分为三步:

  • 消息发送
  • 动态方法解析
  • 消息转发

只有这三层保护全部没有才会报错。

猜你喜欢

转载自blog.csdn.net/weixin_33716557/article/details/87111519
今日推荐