runtime01-消息机制

OC及其动态性

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

Runtime与消息机制

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

OC对象

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

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

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指针是指向它自己。这样就形成了一个完美的闭环。
事例如下图。

实例对象、类、元类关系

继承体系

消息机制

已经介绍类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
消息发送

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

现在调用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对象寻找实现,防止崩溃的方法


动态方法解析

消息机制--消息转发

如果我们不实现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
消息转发

消息链总计起来如下:

  • 1.查找

1.本类查找方法,若有响应,若无去父类查找;

  1. 父类查找,若有响应,如无去父类查找,直至元类;
  2. 元类有响应,元类无,走消息分发机制
  • 2.消息转发

1.消息重新交给被掉用类,被掉用类可以让自己别的方法替代响应

  • 3.动态方法解析
  1. 被掉用类将方法抛给指定的类, 让它响应该方法
  • 4.消息转发

1.方法重新回到被掉用类自身,被掉用类,手动生成方法签名

  1. 将改消息交给指定的类,让它体自己响应

方法查找不到时,只有(2、3、4)三层保护全没有处理才会报错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,776评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,158评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,232评论 0 7
  • 一、Runtime简介 Runtime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消...
    林安530阅读 1,079评论 0 2
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 771评论 0 2