Swift教程

在iOS使用黑魔法实现一键全局图片变灰白的一种方案

本文主要是介绍在iOS使用黑魔法实现一键全局图片变灰白的一种方案,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

背景介绍

  有些特殊的时候会将APP设置成灰白的主题色,iOS不像在网页中有全局样式滤镜,那在iOS中应该如何便捷有效的实现该功能,接下来就分享我在项目中使用黑魔法(Method Swizzling)进行全局处理的一种实现方法。   

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;
}

复制代码

结语

  通过黑魔法实现全局图片处理,也算有不小对收获吧。但自身对黑魔法只会些皮毛方法,有什么理解不对的地方还请赐教,及时指出。

这篇关于在iOS使用黑魔法实现一键全局图片变灰白的一种方案的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!