美文网首页
002-runtime 实现方法交换

002-runtime 实现方法交换

作者: 紫荆秋雪_文 | 来源:发表于2017-02-24 14:23 被阅读53次

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交换方法来实现

相关文章

  • 002-runtime 实现方法交换

    1、方法调用的一个流程 如上面的例子,是如何调用eat方法的对象方法:存储在类对象的方法列表中类方法:存储在元类的...

  • OC runtime 底层API解析

    关于class的API 交换方法API 替换方法的实现 交换方法的实现

  • RunTime实现

    1:RunTiem交换方法实现 //runTime交换方法实现 // 1,创建已有类的分类,并且实现自己的方...

  • Runtime实例运用-交换方法实现、Method Swizzl

    一.交换两个方法的实现: Runtime还可以交换两个方法的实现。例如:Person有两个方法:study、run...

  • runtime 方法交换

    为 NSString 重写 hasPrefix: 方法实现(交换方法)@implementationNSStrin...

  • iOS 防止方法未实现造成的崩溃

    实现原理基于runtime的方法交换和消息发送机制 方法交换 method_exchangeImplementat...

  • runtime(给分类增加属性)

    常用的地方 1. 动态交换两个方法的实现(特别是实现 交换系统方法的实现) 2. 动态添加对象的成员变量和成员方法...

  • Runtime

    runtime运行时机制1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)2:通过runti...

  • Day3

    1 runtime运行时机制1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)。2:通过ru...

  • method swizzling你应该注意的点

    1.避免交换父类方法 如果当前类未实现被交换的方法而父类实现了的情况下,此时父类的实现会被交换,若此父类的多个继承...

网友评论

      本文标题:002-runtime 实现方法交换

      本文链接:https://www.haomeiwen.com/subject/sqtwwttx.html