Runtime浅析

本文深入探讨了iOS中的Runtime机制,包括类与对象的基础数据结构,如Class、objc_object和元类,以及Category的结构。详细介绍了Runtime如何处理关联对象,如设置、获取和移除关联值的函数。进一步讲解了方法与消息的交互,如SEL、IMP、Method以及消息发送流程。文章还涵盖了消息转发机制的四个步骤,包括动态方法解析、备用接收者、完整转发和抛出异常。最后提到了Method Swizzling技术,展示了如何交换或替换方法实现。
摘要由CSDN通过智能技术生成


RuntimeiOS装逼必备,接下来将会详细的说下Runtime


一、Runtime简介


二、类与对象基础数据结构


1.class类型

class类型表示为:

typedef struct object_class *Class


object_class 结构体为:

struct object_class{

    Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

    Class super_class                        OBJC2_UNAVAILABLE;  // 父类

    const char *name                         OBJC2_UNAVAILABLE;  // 类名

    long version                             OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0

    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;


2.objc_object

objc_object是表示一个类的实例的结构体


struct objc_object{

    Class isa OBJC_ISA_AVAILABILITY;

};

typedef struct objc_object *id;


isa指针指向其类


3.元类(Meta Class)

所有的类自身也是一个对象,meta-class是一个类对象的类,meta-class中存储着一个类的所有类方法。

任何NSObject继承体系下的meta-class都使用NSObjectmeta-class作为自己的所属类,而基类的meta-classisa指针是指向它自己。


4.Category

Category结构体


typedef struct objc_category *Category

struct objc_category{

    char *category_name                         OBJC2_UNAVAILABLE; // 分类名

    char *class_name                            OBJC2_UNAVAILABLE;  // 分类所属的类名

    struct objc_method_list *instance_methods   OBJC2_UNAVAILABLE;  // 实例方法列表

    struct objc_method_list *class_methods      OBJC2_UNAVAILABLE; // 类方法列表

    struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE; // 分类所实现的协议列表

}


三、runtime关联对象

1.设置关联值

参数说明:

object:与谁关联,通常是传self

key:唯一键,在获取值时通过该键获取,通常是使用static

const void *来声明

value:关联所设置的值

policy:内存管理策略,比如使用copy


void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)


2.获取关联值

参数说明:

object:与谁关联,通常是传self,在设置关联时所指定的与哪个对象关联的那个对象

key:唯一键,在设置关联时所指定的键


id objc_getAssociatedObject(id object, const void *key)


3.取消关联

void objc_removeAssociatedObjects(id object)


Runtime关联对象的简单应用:

场景:UIButton增加一个Category,定义一个方法,使用block去实现button的点击回调


UIButton+Addition.h


#import <UIKit/UIKit.h>

#import <objc/runtime.h>    // 导入头文件


// 声明一个button点击事件的回调block

typedef void(^ButtonClickCallBack)(UIButton *button);

@interface UIButton (Addition)


// UIButton增加的回调方法

- (void)handleClickCallBack:(ButtonClickCallBack)callBack;


@end


UIButton+Addition.m


#import "UIButton+Addition.h"


// 声明一个静态的索引key,用于获取被关联对象的值

static char *buttonClickKey;

@implementation UIButton (Addition)


- (void)handleClickCallBack:(ButtonClickCallBack)callBack {

    // button的实例与回调的block通过索引key进行关联:

    objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    

    // 设置button执行的方法

    [self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];

}


- (void)buttonClicked {

    // 通过静态的索引key,获取被关联对象(这里就是回调的block

    ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);

    

    if (callBack) {

        callBack(self);

    }

}


@end


四、方法与消息

1.SEL

SEL是表示一个方法的selector的指针。

定义: typedef struct objc_selector *SEL   runtime的源码内没有找到具体的objc_selector定义。


通过下面三种方法可以获取SEL:

asel_registerName函数

bObjective-C编译器提供的@selector()

cNSSelectorFromString()方法


2IMP

IMP是一个函数指针,指向方法实现的地址。

定义:

id (*IMP)(id, SEL,...)

第一个参数:是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)

第二个参数:是方法选择器(selector)

接下来的参数:方法的参数列表。


SEL就是为了查找方法的最终实现IMP


3Method

Method用于表示类定义中的方法

typedef struct objc_method *Method

struct objc_method{

    SEL method_name      OBJC2_UNAVAILABLE; // 方法名

    char *method_types   OBJC2_UNAVAILABLE;

    IMP method_imp       OBJC2_UNAVAILABLE; // 方法实现

}


4、方法调用流程

[receiver message] 转化为

objc_msgSend(receiver, selector) 或者

objc_msgSend(receiver, selector, arg1, arg2,...)


当消息发送给一个对象时首先从运行时系统缓存使用过的方法中寻找。

如果找到,执行该方法,如未找到继续执行下面的步骤


objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector

如果没有找到selectorobjc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector

依此,会一直沿着类的继承体系到达NSObject类。


5、消息转发

当我们不能确定一个对象是否能接收某个消息时,会先调用respondsToSelector:来判断一下:

if([self respondsToSelector:@selector(method)]){

    [self performSelector:@selector(method)];

}


当一个对象无法接收某一消息时,就会启动所谓消息转发(message forwarding)”机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,通过控制台,我们可以看到异常信息。这段异常信息实际上是由NSObject“doesNotRecognizeSelector”方法抛出的。


消息转发机制基本上分为三个步骤:

a、动态方法解析

对象在接收到未知的消息时,首先会调用所属类的类方法

+resolveInstanceMethod:(实例方法)或者

+resolveClassMethod:(类方法)


在这个方法中,我们有机会为该未知消息新增一个处理方法,通过运行时class_addMethod函数动态添加到类里面就可以了。

这种方案更多的是为了实现@dynamic属性。//@dynamic告诉编译器,属性的settergetter方法由用户自己实现,不自动生成。


b、备用接收者

如果在上一步无法处理消息,则Runtime会继续调以下方法

- (id)forwardingTargetForSelector:(SEL)aSelector


返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。


测试代码

//转发目标类

@interface NoneClass : NSObject

@end


@implementation NoneClass

+(void)load

{

    NSLog(@"NoneClass _cmd: %@", NSStringFromSelector(_cmd));

}


- (void) noneClassMethod

{

    NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));

}

@end


@implementation MyTestObject

//…

//将消息转出某对象

- (id)forwardingTargetForSelector:(SEL)aSelector

{

    NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));

    

    NoneClass *none = [[NoneClass alloc] init];

    if ([none respondsToSelector: aSelector]) {

        return none;

    }

    

    return [super forwardingTargetForSelector: aSelector];

}

//…

@end


c、完整转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。


首先要通过,指定方法签名,若返回nil,则表示不处理。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

    if ([NSStringFromSelector(aSelector) isEqualToString:@"testInstanceMethod"]){

        return [NSMethodSignature signatureWithObjcTypes:"v@:"];

    }

    return [super methodSignatureForSelector: aSelector];

}


若返回方法签名,则会进入下一步调用以下方法。

//消息重定向,修改实现方法,修改响应对象等。

- (void)forwardInvovation:(NSInvocation)anInvocation

{

    [anInvocation invokeWithTarget:_helper];

    [anInvocation setSelector:@selector(run)];

    [anInvocation invokeWithTarget:self];

}


d、抛出异常

作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。

- (void)doesNotRecognizeSelector:(SEL)aSelector

虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出一定不能让这个函数就这么结束掉,必须抛出异常


五、Method Swizzling

利用 method_exchangeImplementations 来交换2个方法中的IMP

利用 class_replaceMethod 来修改类

利用 method_setImplementation 来直接设置某个方法的IMP


都是偷换了selectorIMP


实例

//NSArray

#import "NSArray+Swizzle.h"



@implementation NSArray (Swizzle)



- (id)myLastObject

{

    id ret = [self myLastObject];

    NSLog(@"**********  myLastObject *********** ");

    return ret;

}

@end


//调换IMP

#import <objc/runtime.h>

#import "NSArray+Swizzle.h"



int main(int argc, char *argv[])

{

    @autoreleasepool {

        

        Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));

        Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));

        method_exchangeImplementations(ori_Method, my_Method);

        

        NSArray *array = @[@"0",@"1",@"2",@"3"];

        NSString *string = [array lastObject];

        NSLog(@"TEST RESULT : %@",string);

        

        return 0;

    }

}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值