有些特殊的时候会将APP设置成灰白的主题色,iOS不像在网页中有全局样式滤镜,那在iOS中应该如何便捷有效的实现该功能,接下来就分享我在项目中使用黑魔法(Method Swizzling)进行全局处理的一种实现方法。
Object-C中每个类都维护着一个方法(Method)列表,Method 则包含 SEL 和其对应 IMP 的信息。在Objective-C中调用一个方法时,其实是向一个对象发送消息SEL,根据(Method)列表找到对应的 IMP 并执行。
黑魔法(Method Swizzling)要做的事情就是把 SEL 和 IMP 的对应关系断开,并和新 IMP 生成对应关系,进行交换,在运行时偷偷替换系统对应的实现方法,有点像Java中的AOP。
交换前:Asel->AImp Bsel->BImp
交换后:Asel->BImp Bsel->AImp
更多Method Swizzling的相关知识可以到网上查找。
因为项目主要使用UIImageView来呈现图片,所以创建一个UIImageView的Category,并在该Category中实现方法交换。先来看看具体实现代码:
// UIImageView+xmtGrayImage.h #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface UIImageView (xmtGrayImage) @end NS_ASSUME_NONNULL_END 复制代码
// UIImageView+xmtGrayImage.m #import "UIImageView+xmtGrayImage.h" #import <objc/runtime.h> #import "UIImage+xmtImage.h" #import "xmtThemeManager.h" @implementation UIImageView (xmtGrayImage) #pragma mark - Swizzling + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method setImage = class_getInstanceMethod(self,@selector(setImage:)); Method xmtSwizzledSetImage = class_getInstanceMethod(self,@selector(xmtSwizzledSetImage:)); ///交换IMP method_exchangeImplementations(setImage, xmtSwizzledSetImage); }); } - (void)xmtSwizzledSetImage:(UIImage *)image { if (ThemeManager.isGrayImage) { /// 图片使用灰白处理 image = [UIImage grayImage:image]; } [self xmtSwizzledSetImage:image]; } 复制代码
在 + (void)load 方法中实现交换代码,可以保证在系统加载该类文件时就执行代码。
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); } static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); 复制代码
而 dispatch_once(&onceToken 就是为了保证中括号内的代码只被执行一次,IMP 始终只被交换一次。
///获取 setImage: 方法的 IMP Method setImage = class_getInstanceMethod(self,@selector(setImage:)); ///获取 xmtSwizzledSetImage: 方法的 IMP Method xmtSwizzledSetImage = class_getInstanceMethod(self,@selector(xmtSwizzledSetImage:)); ///交换 IMP method_exchangeImplementations(setImage, xmtSwizzledSetImage); 复制代码
而这三句话是将UIImageView中的setImage:和xmtSwizzledSetImage:的IMP交换。
接下来再看看交换IMP后的这段代码。
UIImageView *imageView = [UIImageView new]; [imageView setImage:[UIImage imageNamed:@"xxxx.png"]]; [imageView xmtSwizzledSetImage:[UIImage imageNamed:@"xxxx.png"]]; 复制代码
因为对IMP进行交换,所以对 imageView 发送 [imageView setImage:[UIImage imageNamed:@"xxxx.png"]] 消息,系统执行的是 - (void)xmtSwizzledSetImage:(UIImage *)image 方法的实现代码;对 imageView 发送 [imageView xmtSwizzledSetImage:[UIImage imageNamed:@"xxxx.png"] 消息,系统执行的是 - (void)setImage:(UIImage *)image 的实现代码。
这也是为什么在 - (void)xmtSwizzledSetImage:(UIImage *)image 调用 - (void)xmtSwizzledSetImage:(UIImage *)image 不会发生循环调用的原因。
哈哈,是不是有点饶?啥发送消息,分明是调用方法?emmm...在OC中,调用方法就是对对象发送消息,等对象找到对应对IMP才是真正的执行方法,但为了通俗易懂,通常也会把发送消息说成调用方法。
到这,我们想将图片的改为灰白,在 - (void)xmtSwizzledSetImage:(UIImage *)image 中做相应的处理即可,因为在项目中所有对UIImageView控件设置图片都会经过该方法。同理,其他的图片控件也可以用这种方法进行处理。
顺道附一段对图片进行灰白处理对代码,有透明图层对也可以用。
+ (UIImage *)grayImage:(UIImage *)sourceImage { int width = sourceImage.size.width; int height = sourceImage.size.height; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); CGContextRef context = CGBitmapContextCreate(nil,width,height,8,0,colorSpace,kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(colorSpace); if (context == NULL) { return nil; } CGContextDrawImage(context,CGRectMake(0, 0, width, height), sourceImage.CGImage); CGImageRef grayImageRef = CGBitmapContextCreateImage(context); UIImage *grayImage = [UIImage imageWithCGImage:grayImageRef]; CGContextRelease(context); CGImageRelease(grayImageRef); return grayImage; } 复制代码
通过黑魔法实现全局图片处理,也算有不小对收获吧。但自身对黑魔法只会些皮毛方法,有什么理解不对的地方还请赐教,及时指出。