您好,欢迎来到二三四教育网。
搜索
您的当前位置:首页002-runtime 实现方法交换

002-runtime 实现方法交换

来源:二三四教育网

1、方法调用的一个流程

  • 如上面的例子,是如何调用eat方法的
    • 对象方法:存储在类对象的方法列表中
    • 类方法:存储在元类的方法列表中
  • 寻找过程
    • 通过isa指针去对应的类中查找这个 eat:方法
    • 首先去类中的方法编号区去查找是否有eat:方法对应的编号(每当定义的方法的时候,都会在类的“方法编号区”注册一个编号)
    • 如果有这个编号,然后根据eat:的编号去类的“方法列表”中查找(方法列表中存储的只是最终函数实现的地址)
    • 根据这个函数地址去 “方法区” 调用对应的函数
p调用方法过程.png

2、方法交换的应用场景

  • 需求:当是[UIImage imageNamed:@"图片名"]设置图片的时候,要知道这个图片是否有内容,并且要知道是哪个“图片名”不存在而造成的

    • 策略一:当在使用[UIImage imageNamed:@"图片名"]的时候去判断并且输出造成失败的“图片名”。缺点:只要用到[UIImage imageNamed:@"图片名"]的地方就需要判断,代码会很臃肿。而且如果是旧项目这样的策略就更不合适了
    • 策略二:自定义一个UIImage,在内部实现这些功能。缺点:每次加载一个图片的时候都需要使用自定义的方法,这样是可以实现的,但是当这是一个旧项目的时候,这个方法也就不是一个好的策略。
    • 策略三: 给UIImage添加分类,重写imageNamed:方法,但是这样会覆盖系统的方法(系统优先调用分类中的方法),这个策略也不够好
    • 策略四:给UIImage添加分类,在分类中实现一个有扩展功能的方法,当调用imageNamed:方法的时候使用runtime来交换这个由扩展功能的方法
  • 使用runtime实现方法交换需要注意:防止“死循环”

//
//  UIImage+image.m
//  002-runtime(方法交换)
//
//  Created by 紫荆秋雪 on 2017/2/24.
//  Copyright © 2017年 Revan. All rights reserved.
//

#import "UIImage+image.h"
#import <objc/runtime.h>

@implementation UIImage (image)
// 把类加载进内存的时候调用,只会调用一次
+ (void)load {
    // 获取imageNamed方法
    // 获取哪个类的方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 获取需要交换的方法
    Method revan_imageNamedMethod = class_getClassMethod(self, @selector(revan_imageNamed:));
    // 使用runtime来交换方法
    method_exchangeImplementations(imageNamedMethod, revan_imageNamedMethod);
}

//会被多次调用
//+ (void)initialize {
//
//}

+(UIImage *)revan_imageNamed:(NSString *)name {

    UIImage *image = [UIImage imageNamed:name];
    /// 扩展功能
    if (image) {
        NSLog(@"图片加载成功");
    } else {
        NSLog(@"图片加载失败-%@", name);
    }
    return image;
}

@end

  • 调用方法
//
//  ViewController.m
//  002-runtime(方法交换)
//
//  Created by 紫荆秋雪 on 2017/2/24.
//  Copyright © 2017年 Revan. All rights reserved.
//

#import "ViewController.h"
#import "UIImage+image.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *img = [UIImage imageNamed:@"1.png"];
    NSLog(@"%@", img);
}

@end

  • 为什么会出现“死循环”?
    • 当没有进行“方法交换”的时候,方法调用的过程
方法调用过程.png
  • 当方法交换之后,方法调用的过程
runtime交换方法后调用方法过程.png
  • runtime方法交换造成“死循环”原因:

  • 当调用imageNamed:方法的时候,会在会在方法编号区寻找,如果有就会找到存储方法实现的入口,此时由于已经方法交换,所以此地址是指向方法区revan_imageNamed方法实现,所以就会进入分类中定义的有扩展功能的revan_imageNamed的方法中,会执行[UIImage imageNamed]方法,会再次进入UIImage的方法编号区查询是否存在,和上面的过程一样依然会进入revan_imageNamed方法的实现中,这样就造成了“死循环”

  • 正确的runtime交换方法

//
//  UIImage+image.m
//  002-runtime(方法交换)
//
//  Created by 紫荆秋雪 on 2017/2/24.
//  Copyright © 2017年 Revan. All rights reserved.
//

#import "UIImage+image.h"
#import <objc/runtime.h>

@implementation UIImage (image)
// 把类加载进内存的时候调用,只会调用一次
+ (void)load {
    // 获取imageNamed方法
    // 获取哪个类的方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 获取需要交换的方法
    Method revan_imageNamedMethod = class_getClassMethod(self, @selector(revan_imageNamed:));
    // 使用runtime来交换方法
    method_exchangeImplementations(imageNamedMethod, revan_imageNamedMethod);
}

//会被多次调用
//+ (void)initialize {
//
//}

+(UIImage *)revan_imageNamed:(NSString *)name {

    UIImage *image = [UIImage revan_imageNamed:name];
    /// 扩展功能
    if (image) {
        NSLog(@"图片加载成功");
    } else {
        NSLog(@"图片加载失败-%@", name);
    }
    return image;
}

@end

  • 调用方法
//
//  ViewController.m
//  002-runtime(方法交换)
//
//  Created by 紫荆秋雪 on 2017/2/24.
//  Copyright © 2017年 Revan. All rights reserved.
//

#import "ViewController.h"
#import "UIImage+image.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *img = [UIImage imageNamed:@"1.png"];
    NSLog(@"%@", img);
}

@end

  • 这次调用方法的过程
    • 当在使用系统的UIImage *img = [UIImage imageNamed:@"1.png"];方法的时候,会在UIImage的方法编号区查询是否存在imageNamed:方法,如果存在就会通过"方法列表区"中的地址找到“方法区”中的函数实现,也就是revan_imageNamed:方法,在这个方法实现中有调用了UIImage *image = [UIImage revan_imageNamed:name];所以会在UIImage的方法编号区查找是否存储revan_imageNamed:的编号,如果有再通过“方法列表”中地址,找到“方法区”中的函数实现也就是系统方法 imageNamed方法。
runtime交换方法后调用方法过程.png

3、小结

  • 当希望给系统提供的方法扩展功能的时候,可以考虑使用runtime交换方法来实现

Copyright © 2019- how234.cn 版权所有 赣ICP备2023008801号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务