一个app在更新的时候如果需要更换一套新的图标,在不改动源代码的情况下,如何快速地完成这一任务呢?这种情况就可以使用方法交换来实现这个功能.
为了以后使用方便,我在这里写了一个分类,以后需要的时候就可以直接拿过来使用啦😄.
#import "UIImage+Skin.h"
#import <objc/runtime.h>
@implementation UIImage (Skin)
+ (void)load {
// 获取原始的方法
Method originMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
// 获取新的方法
Method newMethod = class_getClassMethod([UIImage class], @selector(customImageNamed:));
// 交换方法
method_exchangeImplementations(originMethod, newMethod);
}
+ (UIImage *)customImageNamed:(NSString *)name {
NSString *skinFolderPath = [NSString stringWithFormat:@"%@/skins/%@",[NSBundle mainBundle].resourcePath,@"black"];
NSString *path = nil;
if([name hasSuffix:@".png"]) {
path = [NSString stringWithFormat:@"%@/%@",skinFolderPath,name];
} else {
path = [NSString stringWithFormat:@"%@/%@.png",skinFolderPath,name];
}
UIImage *image = [UIImage imageWithContentsOfFile:path];
if(image == nil) {
image = [UIImage customImageNamed:name];
}
return image;
}
@end
随便找了两张图来看下效果,不要在意图片颜值😢.
在控制器中直接生成UIImage对象,并进行赋值.
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView.image = [UIImage imageNamed:@"avatar2"];
}
当程序启动时所看到的图片并不是预先赋值时使用的图片avatar2,而是图片avatar1,也就是说我们使用运行时的方法交换达到了我们想要的结果.而在实际需求中我们只需要在分类中添加如上代码,并根据图片名称拼接出对应的字符串就可以在不更改源码的情况实现项目的需求.
不过这里有一点需要注意,当程序启动时方法已经被加载到内存,使用Method进行方法交换时已经改变了该方法的IMP指针的指向,如下图:
如果把分类中的代码稍作更改,比如这样:
#import <objc/runtime.h>
@implementation UIImage (Skin)
+ (void)load {
// 获取原始的方法
Method originMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
// 获取新的方法
Method newMethod = class_getClassMethod([UIImage class], @selector(customImageNamed:));
// 交换方法
method_exchangeImplementations(originMethod, newMethod);
}
+ (UIImage *)customImageNamed:(NSString *)name {
// 注意点
return [UIImage imageNamed:name];
}
@end
那么再运行程序就会直接崩溃,出现死循环.
因为此时内存中方法的IMP指针已经发生了变化,如果继续使用系统自带的
imageNamed:
的方法,则实际上是在 customImageNamed:
内部又再一次调用了 customImageNamed:
方法,这就造成了方法的循环调用导致死循环.
另外附赠一篇对运行时讲解的比较到位的文章,虽然我看的不是很懂😄,下面是链接:Objc Runtime 深入学习