Runtime小记

一、runtime验证Objective-C语言的底层实现(消息发送机制)

1,首先我们创建一个person类,添加一个实例方法eat,并实现eat方法

#import <Foundation/Foundation.h>


@interface Person : NSObject


- (void)eat;


@end


#import "Person.h"


@implementation Person


- (void)eat{

    NSLog(@"吃了");

}


@end


2,然后我们在ViewController里面来调用这个方法,首先我们导入#import "Person.h",并且实例化对象 ,我这里以2种常用方式来调用的方法,后面以消息发送机制来实现

Person *p = [[Person alloc] init];

方式1:[p eat];

方式2(通过performselector方法编号方式调用):[p performSelector:@selector(eat)];

performselector拓展:@selector() ---> Implementation(IMP:函数指针)  通过方法编号可以找到这个方法实现

方式3(消息发送机制):objc_msgSend(<#id  _Nullable self#>, <#SEL  _Nonnull op, ...#>)

使用这个方法需要导入#import <objc/message.h>头文件,而且依然是报错了,是编译时的错误(苹果不让你使用),因为Xcode自从5.0开始,苹果就不建议大家直接使用消息发送机制了,为什么不建议大家直接使用呢,因为RunTime的出现,运行时机制的出现,我们所写的OC代码底层都用运行时帮助你包装成了运行时的C语言代码,你会发现里面走的就是消息机制。


我们想要验证首先需要关掉Xcode的编译消息机制的检查,打开Build Settings,搜索msg,Enable Strict Checking of objc_msgSend calls 设置为NO。



objc_msgSend(p, @selector(eat));(第一个参数对象,第二个参数方法编号),运行结果成功,我这里就不截屏了。接下来我们既然了解了OC实现是以消息发送机制来做的,我们模拟一下,用消息发送机制的方式来实例

Person *p = [[Person allocinit];

第一步在堆内开辟空间

Person *p = objc_msgSend([Person class], @selector(alloc));

在这里我想补充一下栈与堆内存的相关知识,栈的平衡:函数调用前后,栈指针是只想同一个地方的,栈:是系统分配的,堆:是程序员分配的,这里就涉及到了一个内存泄漏的问题,就是因为内存一直不释放,而且内存释放并不是把这块区域删除掉了,只是告诉系统这块区域可以用了!这块我提出一个问题:当内存释放之后,这块区域还有数据吗???


第二步初始化(依然是发送消息):

 p  = objc_msgSend(p, @selector(init));


运行结果


综上所述,让我门来看一下OC变异成的C++代码

重新创建一个工程,选择command Line Tool(命令行工具)



创建Person类,然后在main里面导入头文件,只需要初始化person就可以了

#import <Foundation/Foundation.h>

#import "Person.h"

#import <objc/message.h>


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

    @autoreleasepool {


        Person *p = [[Person alloc] init];

    }

    return 0;

}


然后show in finder这个main文件,打开终端 cd 到目录下,执行ls命令,执行命令重写main.m

clang -rewrite-objc main.m 这里生成了一个main.cpp文件,打开这个文件





这就是初始化的person代码,是不是很熟悉,嗯,就是消息发送机制


二、runTime的运行时机制

    苹果为我们提供一个一整套API,是相对底层的C语言API

    runtime是OC的底层实现,可以进行一些非常底层的操作,用OC无法实现的

    #import <objc/runtime.h>  

    typedef struct objc_method *Method; 成员方法

    typedef struct objc_ivar *Ivar;     成员变量


这里带入一个HOOK思想(面向切面编程),改变原有的方法,动态的去修改这个方法

利用runtime来实现方法欺骗

下面一个应用场景来解决OC不严谨的问题

 NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/中文"];

 NSURLRequest *request = [NSURLRequest requestWithURL:url];

 NSLog(@"%@",request);


当创建一个url的时候,若果url错误,那么下面创建NSURLRequest也就没有了意义,我门可以这样解决

为url创建一个cagetory,可以进行一个方法的扩展来判断路径是否正确,这就到这我每当用到路径的时候都需要先导入头文件,在替换方法

+ (instancetype)HK_urlWithString:(NSString *)str{

    

    NSURL *url = [NSURL HK_urlWithString:str];

    if (url == nil) {

        NSLog(@"空了");

    }

    return url;

}

然而我们可以使用HOOK思想来做这件事,这里需要用到两个方法

HOOK:勾住一个方法,动态改变这个方法

class_getClassMethod                获取方法

method_exchangeImplementations      交换方法


具体实现

//当这个累加载进内存的时候

+(void)load{

    

    //获取method

    Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));

    Method HK_urlWithString = class_getClassMethod([NSURL class], @selector(HK_urlWithString:));

    

    //交换方法

    method_exchangeImplementations(URLWithString, HK_urlWithString);

    

}




+ (instancetype)HK_urlWithString:(NSString *)str{

    

    NSURL *url = [NSURL HK_urlWithString:str];

    if (url == nil) {

        NSLog(@"空了");

    }

    return url;

}




这样我们每次使用URLWithString的时候都会偷偷的把这个方法替换掉,进行一次判断!这样做是不是很好呢!,但是这样做有一个严重的问题,慎用,有可能对你的小伙伴造成疑惑(如果对runtime不了解的话)!方法固然好用,但是一定要注意应用场景!!!

在此附上一幅Hank老师的图


   

三、动态添加方法

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    

    NSLog(@"%@",NSStringFromSelector(sel));

    

    /*

     cls: 类类型

     sel: 方法编号

     imp: 方法实现(函数指针)

     types:(返回值&参数 类型)

     */

    class_addMethod([Person class], sel, (IMP)eat"v@:@");

    

    return [super resolveInstanceMethod:sel];

}


//OC方法的隐式参数

//1、方法的调用者 id self

//2、方法编号  SEL _cmd

void eat(id selfSEL _cmd,NSString *str){

    NSLog(@"吃了%@---%@---%@",str,self,NSStringFromSelector(_cmd));

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值