iOS开发 - RunTime大法好!

1.概述

Objective-C使用的是small talk语法,在Objective-C中,调用方法有三种方式:

  1. 消息传递
  2. 方法编号perform selector
  3. 消息发送机制
//调用Person类的eat方法的三种方式:
Person * p = [[Person alloc] init];

//1. 直接调用
[p eat];
//2. 通过方法编号
[p performSelector:@selector(eat)];
//3. 通过运行时使用objc_msgSend
/*
 * 要先加载objc/message.h 头文件
 */
#import 

- (void)viewDidLoad {
    //ios5.0之后不建议使用
    
    /*
     * objc_msgSend方法的两个参数:
     * 1. 对象
     * 2. 方法编号
     * Person是类对象,使用class方法就可以获取对象
     */
     
    //相当于 Person * p = [[Person alloc] init];
    Person * p = objc_msgSend([Person class], @selector(alloc));
    p = objc_msgSend(p, @selector(init));
    
    //相当于[p eat];
    objc_msgSend(p, @selector(eat));
}

oc方法由两个部分组成:

  1. SEL 方法编号
  2. IMP 方法实现(函数指针)

关于堆栈

栈:系统分配的(局部变量)
堆:程序员开辟的
alloc在堆内存开辟

内存释放 —— 当内存释放之后,这块区域还有数据吗?

不一定。
有数据,内存释放不一定把数据擦除。

栈的平衡:
函数调用都会遵循一个规则:函数调用前后,栈指针是指向一个地方的

2.Runtime

所有oc的代码,都转成Runtime的C语言代码

Runtime是C语言的API。

可以利用Runtime做什么?

  1. 底层操作(用oc无法实现的),引入头文件查看API

必须掌握的

先导入runtime头文件

#import 

/// An opaque type that represents a method in a class definition.
//成员方法
typedef struct objc_method *Method;

/// An opaque type that represents an instance variable.
//成员变量
typedef struct objc_ivar *Ivar;

面向切面编程:HOOK思想,核心就是Runtime。

方法欺骗:SEL——IMP(函数指针)

          //一一对应
    SEL              IMP
viewDidLoad  ->  viewDidLoad

案例一:URL为空无法判断

NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);

优化:给NSURL添加判断是否为空的功能:

  1. 创建分类category,但是每个文件都要加载该头文件,整个项目都要改变
  2. HOOK思想:钩住一个方法,动态改变方法的实现

使用运行时进行方法实现的交换

1.在哪里进行方法交换?

+load()里面进行交换。

程序在手机硬盘里,是一堆二进制数据,叫指令

CPU从硬盘装载指令到内存,此时调用+load函数,但是还没有执行。

CPU从内存读指令的时候才执行代码。

load比main早加载。

因此在load进行方法交换。

2.交换的图示

[图片上传失败...(image-78f03d-1509985892800)]

3.交换的过程

错误过程

//NSURL+url.m file

+(void)load{
    //两个方法为类方法,因此getclassMethod来获取两个方法
    Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));
    Method Cage_URLWithString = class_getClassMethod([NSURL class], @selector(Cage_URLWithString:));
    
    //进行方法交换
    method_exchangeImplementations(URLWithString, Cage_URLWithString);
}

+(instancetype)Cage_URLWithString:(NSString *)str{
    
    NSURL * url = [NSURL URLWithString:str];
    if (url == nil) {
        NSLog(@"url为空!");
    }
    return url;
}

以上代码错误在于,由于两个方法的imp已经进行了交换,因此在Cage_URLWithString:中调用的URLWithString: = Cage_URLWithString:,无限递归,导致错误,应该将URLWithString:改成Cage_URLWithString:


正确

+(void)load{
    //两个方法为类方法,因此getclassMethod来获取两个方法
    Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));
    Method Cage_URLWithString = class_getClassMethod([NSURL class], @selector(Cage_URLWithString:));
    
    //进行方法交换
    method_exchangeImplementations(URLWithString, Cage_URLWithString);
}

+(instancetype)Cage_URLWithString:(NSString *)str{
    
    NSURL * url = [NSURL Cage_URLWithString:str];
    if (url == nil) {
        NSLog(@"url为空!");
    }
    return url;
}

总结:

1.注意交换的时机

2.修改常用方法的时候,要做好注释

3.小知识点

子类继承父类,不用自父类的方法,在实现的时候,由于子类没有方法,就会向上寻找,找到父类有这个方法,于是实现,实际上子类并不拥有这个方法。

如果想要子类拥有父类的方法,就要override。

如果想要子类拥有父类的行为,就要super

//例如
- (void)viewDidLoad {
[super viewDidLoad];
//无法判断URL是否为空
NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);

}

//子类敷写了父类的viewDidLoad,于是拥有了这个方法
//调用了[super viewDidLoad],拥有了父类的行为

2.动态添加方法

概述:
1.调用一个没有声明和实现的方法

2.关于resolveClassMethodresolveInstanceMethod

3.利用addMethod来动态添加方法

class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)

四个参数:

  1. cls:方法的类
  2. SEL:方法编号
  3. IMP:方法实现(函数指针)(函数名)
  4. types:返回值和参数类型

代码示例

//当类被调用了一个没有实现的类方法
//+(BOOL)resolveClassMethod:(SEL)sel{
//
//}

//当类被调用了一个没有实现的实例方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
    class_addMethod([Person class], sel, eat, "");
    
    return [super resolveInstanceMethod:sel];
}

void eat(){
    NSLog(@"eat or not?");
}

3.KVO源码分析

日后补上。

你可能感兴趣的:(iOS开发 - RunTime大法好!)