iOS-runtime初识

一、runtime简介

  • Objective-C是一种动态语言,其中最主要的是消息机制。理解 Objective-C 的 runtime 机制可以适当的时候对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。
  • Objective-C中一切皆对象,我们知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。

二、runtime详解

在这里插入图片描述
Objective-C中有这样几个文件,这里主要介绍runtime.h和message.h这两个文件中的相关方法使用。runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。

1、类

Class代表一个类,它在objc.h中这样定义的

/// objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

下面我们来看看objc_class到底是什么,在runtime.h中这样定义的

struct objc_class {
	// 好熟悉啊,实例的isa指向类对象,类对象的isa指向元类
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
	// 指向父类
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    // 类名
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    // 成员变量列表
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    // 方法列表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    // 缓存:一种优化,调用过的方法存入缓存列表,下次调用先找缓存
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    // 协议列表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),类对象的isa指针指向的我们称之为元类。

2、实例

objc.h中这样定义的

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

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

可以看到,这个结构体只有一个属性,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。这个时候再来看一个经典图:
在这里插入图片描述
来捋一捋这个逻辑:
objc_object结构体实例它的isa指针指向类对象,类对象的isa指针指向了元类,super_class指针指向了父类的类对象,而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己

3、方法

struct objc_method {
	// 方法名
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    // 方法类型
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    // 方法实现
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}
  • SEL
objc.h
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。两个类之间,只要方法名相同,那么方法的SEL就是一样的。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法。

不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。

在寻找IMP的地址时,runtime提供了两种方法:

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
  • IMP
objc.h
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

第一个参数:是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
第二个参数:是方法选择器(selector)
接下来的参数:方法的参数列表。

4、发送消息

在Objective-C中,消息直到运行时才绑定到方法实现上。

// message.h
objc_msgSend(void /* id self, SEL op, ... */ )

1、objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法列表里面查找方法的selector。
2、如果没有找到selector,objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。
3、依此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现,并将该方法添加进入缓存中
4、如果最后没有定位到selector,则会走消息转发。

5、消息转发

当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象无法接收指定消息时,就会出现报错:

unrecognized selector sent to instance 0x7f91cfd0c200

如果想避免出现这种情况,可以通过respondsToSelector:进行校验,但是这里不做介绍了,下面着重介绍消息转发机制。
在这里插入图片描述

  • 动态解析
    对象在接收到未知的消息时,首先会调用所属类的类方法
    +resolveInstanceMethod:(实例方法)或者
    +resolveClassMethod:(类方法)。
    让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(look)];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(look)) { //如果是执行look函数,就动态解析,指定新的IMP
        class_addMethod([self class], sel, (IMP)lookMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void lookMethod(id obj, SEL _cmd) {
    NSLog(@"new look");//新的look函数
}

// 打印结果
2020-08-23 13:26:32.342039+0800 ModuleProject[7610:289344] new look

可以看到没有实现look这个函数,但是我们通过class_addMethod动态添加lookMethod函数,并执行lookMethod这个函数的IMP。从打印结果看,成功实现了。
如果resolve方法返回 NO ,运行时就会移到下一步:forwardingTargetForSelector

  • 备用接收者
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(look)];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(look)) {
        return NO;
    }
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(look)) {
        return [BViewController new]; //返回BViewController对象,让BViewController对象接收这个消息
    }
    return [super forwardingTargetForSelector:aSelector];
}

打印结果
2020-08-23 13:36:17.753347+0800 ModuleProject[7787:297279] new look

可以看到我们通过forwardingTargetForSelector把当前ViewController的方法转发给了BViewController去执行了。打印结果也证明我们成功实现了转发。
如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

  • 完整消息转发
    如果在上面还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(look)];

}

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

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

// 获得函数的参数和返回值类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"look"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    return [super methodSignatureForSelector:aSelector];
}

// 发送消息给目标对象
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    BViewController *b = [BViewController new];
    if([b respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:b];
    }
    else {
        [self doesNotRecognizeSelector:sel];
    }
}

通过签名,runtime生成了一个对象anInvocation,发送给了forwardInvocation,我们在forwardInvocation方法里面让BViewController对象去执行了look函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值