关于runtime

一.概述

Runtime是一套C语言的API,基本是用 C 和汇编写的,封装了很多动态性相关的函数,在这里下到苹果维护的开源代码。主要是使用官方Api,解决我们框架性的需求。

Objective-C是一门动态语言。我们平时编写的OC代码,底层都是转换成了Runtime API进行调用。

动态:就是编译器在编译期可以只知道一个方法的名字,而不需要知道这个方法的实现,只有在运行期间调用该方法的时候,才根据方法名去找到对应方法的实现。

二.消息传递

Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行消息转发。

当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应消息而做出不同的反应。这就是消息传递。

当执行[object doSomething]时,编译器转成消息发送objc_msgSend(object, doSomething),如果有参数则为objc_msgSend(object, doSomething,arg1,arg2,....) runtime的执行流程:

  1. 首先,通过objectisa指针找到它的 class ;
  2. classmethod listdoSomething ;
  3. 如果 class 中没到 doSomething,继续往它的 superclass 中找 ;
  4. 一旦找到 doSomething 这个函数,就去执行它的实现IMP ;

object的结构体

//对象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

class 的结构体

//类
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#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;

isa:是一个指向objc_class结构体的指针,而isa 是它唯一的私有成员变量,即所有对象都有isa指针(isa位置在成员变量第一个位置)

#if !__OBJC2__
    Class _Nullable super_class      /*父类*/                   
    const char * _Nonnull name       /*类名*/                      
    long version                    /*版本信息*/                  
    long info                       /*类信息*/                       
    long instance_size              /*实例大小*/                      
    struct objc_ivar_list * _Nullable ivars     /*实例参数链表*/             
    struct objc_method_list * _Nullable * _Nullable methodLists     /*方法链表*/              
    struct objc_cache * _Nonnull cache                        /*方法缓存*/          
    struct objc_protocol_list * _Nullable protocols                 /*协议链表*/      
#endif

} OBJC2_UNAVAILABLE;

方法列表:

//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
} 

方法

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

IMP

/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 
#endif

在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

三.消息转发

如果在方法列表中找不到该方法,就进行消息转发。消息转发的三个步骤: 动态方法解析,备用接收者,完整消息转发。

消息转发

1.动态方法解析 Objective-C运行时会调用 +resolveInstanceMethod:(动态解析实例方法)或者 +resolveClassMethod:(动态解析类方法),可以提供一个函数实现,如果添加了函数并返回YES,那运行时系统就会重新启动一次消息发送的过程。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(gotoOpen)];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel==@selector(gotoOpen)){
        class_addMethod([ self class], sel,(IMP)openMethod,"v@:");
        return YES;
    }
      return  [super resolveInstanceMethod:sel];
}

void openMethod(id obj,SEL _cmd){
    NSLog(@"打开");
}

总结:虽然没有实现方法gotoOpen,但是我们通过class_addMethod添加了方法openMethod,并执行openMethod这个函数的IMP。

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Class cls: 将要给添加方法的类,传的类型 [类名  class]
SEL name: 将要添加的方法名,传的类型   @selector(方法名) 
IMP imp:实现这个方法的函数 ,传的类型   1,C语言写法:(IMP)方法名    2,OC的写法:class_getMethodImplementation(self,@selector(方法名:))
//OC写法
class_addMethod([ self class], sel,class_getMethodImplementation(self, @selector(openMethod)),"v@:");

-(void)openMethod{
    NSLog(@"打开");
}

const char *types:表示我们要添加的方法的返回值和参数

"v@:":v:是添加方法无返回值 @表示是id(也就是要添加的类) :表示添加的方法类型 @表示:参数类型

2.备用接收者 如果动态解析返回为NO,则执行forwardingTargetForSelector,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(openMethod)];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    return NO;
}

-(id)forwardingTargetForSelector:(SEL)sel{
    if(sel==@selector(openMethod)){
        return [Person new];
    }
    return [super forwardingTargetForSelector:sel];
}

Person.m

-(void)openMethod{
    NSLog(@"调用方法openMethod");
}

让备用的对象(Person)去响应了本身无法响应的一个SEL(openMethod)

3.完整消息转发 如果上一步返回nil,执行methodSignatureForSelector的方法手动将响应方法切换给备用响应对象。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(openMethod)];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    return NO;
}

-(id)forwardingTargetForSelector:(SEL)sel{
    return nil;
}

-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    if(sel==@selector(openMethod)){
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:sel];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    //获得消息
    SEL sel = [anInvocation selector];
    //转发
    Person *person = [[Person alloc]init];
    if([person respondsToSelector:sel]){
        [anInvocation invokeWithTarget:person];
    }else
    {
        [super forwardInvocation:anInvocation];
    }
}
Person.m
-(void)openMethod{
    NSLog(@"调用方法openMethod");
}

如果拿到签名后的派发对象Person没有实现方法openMethod,则会执行doesNotRecognizeSelector。

-(void)doesNotRecognizeSelector:(SEL)aSelector{
    NSLog(@"此方法不存在");
}

我们经常遇到的Crash问题:unrecognized selector sent to instance 0x100a1c2e0'

调用该方法时,经过消息传递,消息转发都没有找到该方法的实现,程序就会崩溃。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • mpvue中使用vuex注意事项

    报错原因: 在mpvue中使用vuex和在vue中使用是不一样,在Vue中只需要在main.js引用一下

    honey缘木鱼
  • 高德与百度地图坐标的相互转化(IOS和h5)

    honey缘木鱼
  • 完美解决vue项目中弹出框滑动时,内部页面也跟着滑动问题

    //弹出框禁止滑动 Vue.prototype.noScroll = function () { var mo = function (e) { e.pre...

    honey缘木鱼
  • runtime

    作为iOS开发者,runtime特性是必须了解的重点加分项。这并不是说你可以说出消息机制,运行时消息重定向,或者利用runtime特性实现交换方法等,而是更应该...

    sweet说好的幸福
  • IDEA 本地运行 Spark Demo 报错

    运行spark demo时出现java.lang.NoSuchMethodError: scala.Predef$.refArrayOps([Ljava/lan...

    runzhliu
  • Android应用界面开发——简单控件和Activity间传递数据

    要想开发一个Android App,开发环境是必不可少的,所以学习之前应该先搭建环境,环境如下:

    trampcr
  • 理解JWT(JSON Web Token)认证及实践

    HTTP Basic Auth 在HTTP中,基本认证是一种用来允许Web浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式,通常用...

    goodspeed
  • Tensorflow

    ##################################################################

    Dean0731
  • iPhone xs出现新漏洞(含复现视频)

    好像每次新款的iPhone出来都会有类似的问题,按照网上,我们有师傅(绝对的土豪师傅)测试了一下:

    用户5878089
  • 企点课堂 | “互联网+”下的零售行业,如何实现新的飞跃?下周四开讲!

    ? 零售行业在互联网+思维的引导下 如何完成高效的用户增长? 如何提升客单价? 如何实现低成本+高收益的运营模式? …… 下周四下午四点 企点君为你深度讲解 ...

    腾讯企点

扫码关注云+社区

领取腾讯云代金券