上一篇文章介紹了通過UIImage分類的方式實(shí)現(xiàn)換膚功能,但是此方式有一定的局限性:
需要開發(fā)前就考慮到這個功能,如果前期沒有考慮到皮膚處理,開發(fā)中期或后期產(chǎn)品迭代,需求中添加這個功能,再去添加分類修改代碼效率就太低了.這種情況下就可以通過Runtime機(jī)制來交換方法.

exchangeImplementations.png
與基本換膚一樣,在視圖上通過ImageView顯示不同的圖片演示換膚功能,同時為了演示多界面同步換膚,設(shè)置一個TabBarController為根控制器,包含兩個子控制器,如圖:

界面搭建.png
原理與基礎(chǔ)換膚一樣,仍然需要添加一個UIImage分類,在Load方法中通過運(yùn)行時機(jī)制交換方法:
首先導(dǎo)入頭文件<objc/objc.h>
接下來通過運(yùn)行時機(jī)制交換imageNamed和jsImageNamed方法
// 1. 獲取對應(yīng)交換的方法
Method method1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method method2 = class_getClassMethod([UIImage class], @selector(jsImageNamed:));
// 2. 交換方法
method_exchangeImplementations(method1, method2);
- 外界直接使用系統(tǒng)方法imageNamed就可以了,運(yùn)行時已經(jīng)交換了兩個方法的實(shí)現(xiàn)部分
#######需要注意的地方:
1.使用運(yùn)行時機(jī)制交換方法,在App整個生命周期都會交換
2.使用交換方法后,注意死循環(huán)問題:
在自定義的方法中,調(diào)用了系統(tǒng)imageNamed方法,就會造成死循環(huán)(運(yùn)行時交換的是兩個方法的實(shí)現(xiàn)部分,當(dāng)外界調(diào)用imageNamed的時候,實(shí)際執(zhí)行的JSImageNamed的實(shí)現(xiàn)部分,這樣方法內(nèi)部調(diào)用了自己),但這里又要使用系統(tǒng)方法imageNamed,所以要換成自定義的jsImageNamed方法就可以了
// 自定義方法,根據(jù)當(dāng)前皮膚設(shè)置圖片
+ (UIImage *)jsImageNamed:(NSString *)name{
if (isNight) { // 夜間模式
name = [NSString stringWithFormat:@"%@_night",name];
}
return [UIImage jsImageNamed:name];
}
完整代碼:
.h
#import <UIKit/UIKit.h>
@interface UIImage (JSSkin)
// 根據(jù)皮膚設(shè)置圖片
+ (UIImage *)jsImageNamed:(NSString *)name;
// 記錄皮膚
+ (void)saveSkinModeWithNight:(BOOL)night;
// 獲取皮膚設(shè)置
+ (BOOL)isNight;
@end
.m
#import "UIImage+JSSkin.h"
#import <objc/runtime.h>
@implementation UIImage (JSSkin)
// 夜間模式標(biāo)識
static bool isNight;
+ (void)load{
// 獲取偏好設(shè)置中的皮膚模式
isNight = [[NSUserDefaults standardUserDefaults] boolForKey:@"isNight"];
// 使用運(yùn)行時機(jī)制交換方法 一旦交換,在App整個生命周期都會交換
// 1. 獲取對應(yīng)交換的方法
Method method1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method method2 = class_getClassMethod([UIImage class], @selector(jsImageNamed:));
// 2. 交換方法
method_exchangeImplementations(method1, method2);
}
// 自定義方法,根據(jù)當(dāng)前皮膚設(shè)置圖片
+ (UIImage *)jsImageNamed:(NSString *)name{
if (isNight) { // 夜間模式
name = [NSString stringWithFormat:@"%@_night",name];
}
return [UIImage jsImageNamed:name];
}
+ (void)saveSkinModeWithNight:(BOOL)night{
// 賦值,記錄當(dāng)前皮膚狀態(tài)
isNight = night;
// 本地記錄狀態(tài)(偏好設(shè)置)
[[NSUserDefaults standardUserDefaults] setBool:isNight forKey:@"isNight"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
+ (BOOL)isNight{
// 返回當(dāng)前皮膚狀態(tài)
return isNight;
}
@end
外界調(diào)用:
控制器1
#import "PageOneViewController.h"
#import "UIImage+JSSkin.h"
@interface PageOneViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView_1;
@property (weak, nonatomic) IBOutlet UIImageView *imageView_2;
@property (weak, nonatomic) IBOutlet UISwitch *nightModeSwitch;
@end
@implementation PageOneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 獲取當(dāng)前皮膚模式,同步Switch狀態(tài)
self.nightModeSwitch.on = [UIImage isNight];
// 設(shè)置圖片
self.imageView_1.image = [UIImage imageNamed:@"baby"];
self.imageView_2.image = [UIImage imageNamed:@"girl"];
}
// Switch開關(guān)點(diǎn)擊事件
- (IBAction)nightModeSwitchClick:(UISwitch *)sender {
// 本地化存儲(偏好設(shè)置)
[UIImage saveSkinModeWithNight:sender.isOn];
// 設(shè)置圖片
self.imageView_1.image = [UIImage imageNamed:@"baby"];
self.imageView_2.image = [UIImage imageNamed:@"girl"];
}
@end
控制器2
#import "PageTwoViewController.h"
#import "UIImage+JSSkin.h"
@interface PageTwoViewController ()
@property (strong, nonatomic) IBOutlet UIImageView *imageView_3;
@end
@implementation PageTwoViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// 設(shè)置圖片
self.imageView_3.image = [UIImage imageNamed:@"girl"];
}
@end
效果圖: