Runtime 运行时之方法交换。

OC 中可以使用 Runtime 执行方法交换。

什么是方法交换?

方法交换就是两个方法的实现进行交换。
调用 A 方法的时候,实际上调用的是 B 方法的实现。
调用 B 方法的时候,实际上是调用 A 方法的实现。

为什么 OC 的方法可以交换?

OC 的方法从调用层面来说,是给某个对象发送了一个字符串信息(SEL)。

 Person *p = [Person new];
[p eat]; // 跑步

调用过程:

  1. 首先拿到 SEL ,也就是 @selector(eat);
  2. 接着拿到接受这个 SEL 的对象 p
  3. 最后调用 objc_msgSend(p,@selector(run));

然后通过这个 SEL 找到方法的实现。
所以,对于 OC 的方法来讲,它不是一个完整的整体。而是 SEL + IMP。

oc 方法寻迹链

从上图可知,对于 object_method 里的结构体,中包含两个成员字段:

  1. SEL
  2. IMP

所以,也再次说明了,OC 中的方法调用和方法实现并不是一个完整的整体。
这也就是 OC 方法交换的基础。


一个简单的方法交换 demo

所有学习新知识点都是都从简单的 demo 开始的。
(一上来拿具体项目说事的人都太天才了。)

在一个 Person 类中,定义 runeat 两个方法。

@interface Person : NSObject

- (void)run;
- (void)eat;

@end
- (void)run {
    NSLog(@"%@",@"跑步");
}

- (void)eat {
    NSLog(@"%@",@"吃饭");
}

前提: 当执行 run 方法时,控制台会输出 跑步 。 当执行 eat 方法时,控制台会输出 吃饭

目标: 当执行 run 方法时,控制台会输出 吃饭。而当执行 eat 方法是,控制台会输出 跑步

开始前,再次说明一下。

OC 中的方法调用,本质上是消息发送。把 SEL 消息,发给 P 对象。
让后通过 SEL 在 P 对象身上找到包含这个 SEL 的 object_method。
在从这个 object_method 里找到 IMP 方法实现。
并执行这个方法。

所以,OC 中方法的调用和实现是分开的。

我们可以在 +load 方法里,里用 Runtime 来交换两个 SEL 的 IMP。

+ (void)load {
    Method method1 = class_getInstanceMethod([self class], @selector(run));
    Method method2 = class_getInstanceMethod([self class], @selector(eat));

    // 方法交换
    method_exchangeImplementations(method1, method2);
}

这里为什么用 load 方法?有什么说道吗?
load 方法是在当前类被加载打运行时就会执行的方法。

潜台词就是:

当类加载到运行时的时候,类的属性,成员,方法,协议等都已经被加载好了。

然后开始调用当前对象的 eat & run 方法。

Person *p = [Person new];
[p eat]; // 跑步
[p run]; // 吃饭

控制台输出

2017-11-07 13:09:28.826 CodeFor方法交换[22022:19874567] 跑步
2017-11-07 13:09:28.826 CodeFor方法交换[22022:19874567] 吃饭

结论符合预期。两个方法的实现的确是交换了。


方法交换一些比较使用的场景

在 App 开发中,经常会使用到图片。
有些承载图片的 UIImageView 的 size 是确定的。
但提供的素材图片的和 UIImageView 的 size 不一样。
于是,当这张图片显示在 UIImageView 上时,会出现拉伸和压缩的情况。

由于 image.size != UIImageView.size 而出现的拉伸/压缩

可能有的人会说,图片被拉就拉伸,被压缩就压缩呗。无所谓。反正就这一张图。又不会动。

在上述那个场景里,的确是这样。
但是如果这是一个在 UITableViewCell 里的图片呢?
并且用户在快速的滑动这些 cell。
这样就会出现一张被频繁的挪动位置 + 拉伸/压缩。这对性能是一个很大的考验。

出现上述性能问题的主要问题是什么?

图片文件的尺寸和 UIImageView 的尺寸不一致。

解决思路

  1. 在给 UIImageView 设置 image 的时候,调用的不就是 setImage: 方法吗?
  2. 我们交换 UIImageView 的 setImage: 方法为我们自己的 rl_setImage: 方法。
  3. 在我们自己的方法里,生成一张和当前 UIImageView size 一样大的图片。
  4. 然后在调用 UIImageView 原本的 setImage: 方法,把我们生成的这张 size 和 UIImageView 一样大的图片设置到 image 上。
  5. 图片由于和 UIImageView 的 size 不一致导致的 拉伸/压缩的问题不就解决了?

动手实现

第一步:先创建一个 UIImageView 的分类

image.png

为什么要创建一个 UIImageView 的分类?方法交换可以是任意两个对象之间的交换。
我随便拿个对象和 UIImageView 的 setImage 方法交换不就好了?

因为,在 UIImageView 的分类方法里,self关键字表示的是当前 UIImageView 的对象。
我们可以通过 self 快速的拿到当前 UIImageView 的 size。
从而快速设置手动生成的 UIImage 的 size。

代码实现

@implementation UIImageView (runtime)
+ (void)load {
    Method setImage = class_getInstanceMethod([self class], @selector(setImage:));
    Method mySetImage = class_getInstanceMethod([self class], @selector(mySetImage:));

    method_exchangeImplementations(setImage, mySetImage);
}

- (void)mySetImage:(UIImage *)image {

    // 由于,image 来自一张真是存在的图片文件。所以,他的 size 是只读的,不能修改一个真实存储在的图片的尺寸。
    // 画一个大小和当前控制器一样大的 image。而不是使用原来的 image
    // 开启上下文
    UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES, 0);

    [image drawInRect:self.bounds];

    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();

    // 关闭上下文
    UIGraphicsEndImageContext();
    [self mySetImage:result];
    // NSLog(@"图片设置完毕");
}

运行效果:

当 image.size = UIImageView.size 的时候,不会出现图片的拉伸/压缩

这里创建的符合 UIImageView.size 的尺寸图片是在主线程
经过简单的测试发现生成这样一张图片大概耗时:

2017-11-07 14:30:16.836 CodeFor方法交换[22328:19993794] 0.003812

大概 3/1000 秒。

如果对性能要求比较高的话,可以把这个生成图片数据的任务放到子线程执行。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimeInterval start = CACurrentMediaTime();
        UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES, 0);

        [image drawInRect:self.bounds];

        UIImage *result = UIGraphicsGetImageFromCurrentImageContext();

        // 关闭上下文
        UIGraphicsEndImageContext();

        NSLog(@"%f",CACurrentMediaTime() - start);

        // 回到主线程,设置图像
        dispatch_async(dispatch_get_main_queue(), ^{
            [self mySetImage:result];
            
        });
        
    });

这种做法很适合于 UITableViewCell 中包含图片情况的优化。
我们可以通过交换 UIImageView 的 setImage: 方法来提高 UITableViewCell 的性能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 本文分为4个部分 1.介绍OC和C语言之间的转换 2.介绍运行时和相关术语 3.介绍消息发送机制已及怎样找到函数实...
    一片枫叶随风舞阅读 302评论 0 1
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,697评论 7 64
  • 是什么夜 把温柔的雨轻抚进我的心中 整夜的不能睡 寂寞也不能让我独醉 梦把远方拉近 我看见了你的泪 你是七月的一场...
    失天翼阅读 264评论 1 2
  • 照片中站在地上的小孩儿是六十多年前的父亲,我很喜欢这张照片,一半的原因是由于帅气的父亲,遥想当年,父亲跟自己和儿子...
    克洛克_8e5d阅读 440评论 1 2