博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UIWebView、WKWebView 支持 WebP 图片显示
阅读量:3592 次
发布时间:2019-05-20

本文共 9922 字,大约阅读时间需要 33 分钟。

移动端应用往往有大量的图片展示场景,图片的大小对企业至关重要。WebP 作为一种更高效的图片编码格式,平均大小比 PNG/JPG/ GIF / 动态 GIF 格式减少 70%(),且质量没有明显的差别,是其他图片格式极佳的替代者。

一、MagicWebViewWebP.framework 架构

主要文件 说明
MagicWebViewWebPManager (引用文件)管理 MagicURLProtocol 的注册、销毁 MagicURLProtocol
MagicURLProtocol 继承 NSURLProtocol 用于截获 url
NSURLProtocol+MagicWebView 扩展 NSURLProtocol 用于注册、销毁 Scheme
依赖 说明
SDWebImage 使用了 webP 相关部分模块
libwebp webP 依赖文件

二、说明

MagicURLProtocol

MagicURLProtocol 继承于 NSURLProtocol,实现了对 webp 图片网络请求进行拦截,将拦截的请求使用 NSURLConnection(也可以使用 NSURLSession) 加载数据,然后利用 SDWebImage 中 UIImage+WebP 提供加载 webp 图片的能力,并将加载好的 UIImage 转化为 NSData 返回,实现 webp 图片的加载。

1、NSURLProtocol 主要步骤?

注册—>拦截—>转发—>回调—>结束

2、继承 NSURLProtocol 必须实现的方法?

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;- (void)startLoading;- (void)stopLoading;

3、什么是 NSURLProtocol?

NSURLProtocol 作为 URL Loading System 中的一个独立部分存在,能够拦截所有的 URL Loading System 发出的网络请求,拦截之后便可根据需要做各种自定义处理,是 iOS 网络层实现 AOP (面向切面编程) 的终极利器,所以功能和影响力都是非常强大的。

URL Loading System 的图

1.NSURLProtocol 可以拦截的网络请求包括 NSURLSession,NSURLConnection 以及 UIWebVIew。

2. 基于 CFNetwork 的网络请求,以及 WKWebView 的请求是无法拦截的。
3. 现在主流的 iOS 网络库,例如 AFNetworking,Alamofire 等网络库都是基于 NSURLSession 或 NSURLConnection 的,所以这些网络库的网络请求都可以被 NSURLProtocol 所拦截。

//MagicURLProtocol.h#import 
@interface MagicURLProtocol : NSURLProtocol @end
//MagicURLProtocol.m#import "MagicURLProtocol.h"#ifdef SD_WEBP#import "UIImage+WebP.h"#endifstatic NSString *const MagicURLProtocolKey = @"MagicURLProtocol-already-handled";@interface MagicURLProtocol()
@property (strong, nonatomic) NSURLConnection *connection;@property (strong, nonatomic) NSMutableData *recData;@end@implementation MagicURLProtocol- (void)dealloc{ self.recData = nil;}/** 判断是否启用SD_WEBP 并且图片格式为webp 如果为YES 则标记请求需要自行处理并且防止无限循环 为NO则不处理 Build Settings -- Preprocessor Macros, 添加 SD_WEBP=1 */+ (BOOL)canInitWithRequest:(NSURLRequest *)request { BOOL useCustomUrlProtocol = NO; NSString *urlString = request.URL.absoluteString; if (!SD_WEBP || ([urlString.pathExtension compare:@"webp"] != NSOrderedSame)) { useCustomUrlProtocol = NO; }else { if ([NSURLProtocol propertyForKey:MagicURLProtocolKey inRequest:request] == nil) { useCustomUrlProtocol = YES; }else { useCustomUrlProtocol = NO; } } return useCustomUrlProtocol;}+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request;}// 将截获的请求使用NSURLConnection | NSURLSession 获取数据// (此处使用NSURLConnection)- (void)startLoading{ NSMutableURLRequest *newRequest = [self cloneRequest:self.request]; //NSString *urlString = newRequest.URL.absoluteString; //NSLog(@"######截获WebP url:%@",urlString); [NSURLProtocol setProperty:@YES forKey:MagicURLProtocolKey inRequest:newRequest]; [self sendRequest:newRequest];}- (void)stopLoading{ if (self.connection) { [self.connection cancel]; } self.connection = nil;}#pragma mark - dataDelegate//复制Request对象- (NSMutableURLRequest *)cloneRequest:(NSURLRequest *)request{ NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:request.URL cachePolicy:request.cachePolicy timeoutInterval:request.timeoutInterval]; newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields; [newRequest setValue:@"image/webp,image/*;q=0.8" forHTTPHeaderField:@"Accept"]; if (request.HTTPMethod) { newRequest.HTTPMethod = request.HTTPMethod; } if (request.HTTPBodyStream) { newRequest.HTTPBodyStream = request.HTTPBodyStream; } if (request.HTTPBody) { newRequest.HTTPBody = request.HTTPBody; } newRequest.HTTPShouldUsePipelining = request.HTTPShouldUsePipelining; newRequest.mainDocumentURL = request.mainDocumentURL; newRequest.networkServiceType = request.networkServiceType; return newRequest; }#pragma mark - 网络请求- (void)sendRequest:(NSURLRequest *)request{ self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];}#pragma mark - NSURLConnectionDataDelegate/** * 收到服务器响应 */- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ NSURLResponse *returnResponse = response; [self.client URLProtocol:self didReceiveResponse:returnResponse cacheStoragePolicy:NSURLCacheStorageAllowed];}/** * 接收数据 */- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ if (!self.recData) { self.recData = [NSMutableData new]; } if (data) { [self.recData appendData:data]; }}/** * 重定向 */- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response{ if (response) { [self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response]; } return request;}/** * 加载完毕 */- (void)connectionDidFinishLoading:(NSURLConnection *)connection{ NSData *imageData = self.recData;#ifdef SD_WEBP UIImage *image = [UIImage sd_imageWithWebPData:self.recData]; imageData = UIImagePNGRepresentation(image); if (!imageData) { imageData = UIImageJPEGRepresentation(image, 1); }#endif [self.client URLProtocol:self didLoadData:imageData]; [self.client URLProtocolDidFinishLoading:self];}/** * 加载失败 */- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ [self.client URLProtocol:self didFailWithError:error];}@end

MagicWebViewWebPManager

MagicWebViewWebPManager 封装了支持 UIWebView、WKWebView 注册和销毁 MagicURLProtocol。

特别注意:NSURLProtocol 一旦被注册将会使整个 app 的 request 请求都会被拦截,进入 Web 时注册,退出 Web 取消注册。在使用的时候需要特别注意。

//MagicWebViewWebPManager.h#import 
@interface MagicWebViewWebPManager : NSObject+ (MagicWebViewWebPManager *)shareManager;- (void)registerMagicURLProtocolWebView:(id)webView; //注册 MagicURLProtocol- (void)unregisterMagicURLProtocolWebView:(id)webView; //销毁 MagicURLProtocol@end
//MagicWebViewWebPManager.m#import "MagicWebViewWebPManager.h"#import "NSURLProtocol+MagicWebView.h"#import 
static NSString *const MagicURLProtocol_String = @"MagicURLProtocol";@implementation MagicWebViewWebPManager+ (MagicWebViewWebPManager *)shareManager{ static MagicWebViewWebPManager *manger; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manger = [[MagicWebViewWebPManager alloc] init]; }); return manger;}#pragma mark - WebView支持webP// NSURLProtocol 一旦被注册将会使整个App的request请求都会被拦截,进入Web时注册,退出Web取消注册/** 注册 MagicURLProtocol */- (void)registerMagicURLProtocolWebView:(id)webView{ if ([webView isKindOfClass:[WKWebView class]]) { [NSURLProtocol registerClass:NSClassFromString(MagicURLProtocol_String)]; [NSURLProtocol wk_registerScheme:@"http"]; [NSURLProtocol wk_registerScheme:@"https"]; } if ([webView isKindOfClass:[UIWebView class]]){ [NSURLProtocol registerClass:NSClassFromString(MagicURLProtocol_String)]; }}/** 销毁 MagicURLProtocol */- (void)unregisterMagicURLProtocolWebView:(id)webView{ if ([webView isKindOfClass:[WKWebView class]]) { [NSURLProtocol unregisterClass:NSClassFromString(MagicURLProtocol_String)]; [NSURLProtocol wk_unregisterScheme:@"http"]; [NSURLProtocol wk_unregisterScheme:@"https"]; } if ([webView isKindOfClass:[UIWebView class]]){ [NSURLProtocol unregisterClass:NSClassFromString(MagicURLProtocol_String)]; }}@end

NSURLProtocol+MagicWebView

NSURLProtocol+MagicWebView 用于提供 WKWebView 对 NSURLProtocol 的支持能力。

UIWebView 默认支持 NSURLProtocol,如果需要 WKWebView 也支持 NSURLProtocol,则需要扩展,具体代码如下。

//NSURLProtocol+MagicWebView.h#import 
@interface NSURLProtocol (MagicWebView)+ (void)wk_registerScheme:(NSString *)scheme; //注册协议+ (void)wk_unregisterScheme:(NSString *)scheme; //注销协议@end
//NSURLProtocol+MagicWebView.m#import "NSURLProtocol+MagicWebView.h"#import 
FOUNDATION_STATIC_INLINE Class ContextControllerClass() { static Class cls; if (!cls) { cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class]; } return cls;}FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() { return NSSelectorFromString(@"registerSchemeForCustomProtocol:");}FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() { return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");}@implementation NSURLProtocol (MagicWebView)+ (void)wk_registerScheme:(NSString *)scheme { Class cls = ContextControllerClass(); SEL sel = RegisterSchemeSelector(); if ([(id)cls respondsToSelector:sel]) {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks" [(id)cls performSelector:sel withObject:scheme];#pragma clang diagnostic pop }}+ (void)wk_unregisterScheme:(NSString *)scheme { Class cls = ContextControllerClass(); SEL sel = UnregisterSchemeSelector(); if ([(id)cls respondsToSelector:sel]) {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks" [(id)cls performSelector:sel withObject:scheme];#pragma clang diagnostic pop }}@end

三、使用

1、将 MagicWebViewWebP.framework 导入工程。

2、引入头文件。

#import 

3、webView 加载请求之前,注册 MagicURLProtocol。

- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.     // 注册协议支持webP    [[MagicWebViewWebPManager shareManager] registerMagicURLProtocolWebView:self.wkWebView];    // 加载请求    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_requestURLString]];    [self.wkWebView loadRequest:request];}

4、dealloc 中销毁 MagicURLProtocol。

若有特殊需求,需要在 web 页面退出时销毁 MagicURLProtocol,否则会拦截整个 app 的网络请求。

// 销毁-(void)dealloc{    [[MagicWebViewWebPManager shareManager] unregisterMagicURLProtocolWebView:self.wkWebView];}

5、可以加载 来检测 webP 显示效果。

三、开源地址

MagicWebViewWebP.framework 已经开源,喜欢的小伙伴儿可以 Star。⭐️⭐️⭐️⭐️⭐️

参考文章

 

作者:LuisX
链接:https://www.jianshu.com/p/8639c135a4fe
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的文章
windows10解决80端口被占用的问题
查看>>
ElasticSearch快速入门之创建索引库、创建映射、创建文档、搜索文档
查看>>
用故事巧妙帮助理解公钥和私钥的区别和联系
查看>>
application.properties 文件和 application.yml 文件区别以及加载顺序
查看>>
阿里云服务器安装docker,拉取常用的mysql,redis,nginx等镜像
查看>>
为什么timestamp到2038年就截止了?
查看>>
设计模式之适配器模式
查看>>
设计模式之工厂模式
查看>>
设计模式之原型模式
查看>>
设计模式之对象池模式
查看>>
设计模式之责任链模式 Java实例代码 + Tomcat责任链模式应用+安卓责任链模式应用
查看>>
设计模式之命令模式 Java实例讲解 + 线程池中的应用场景
查看>>
设计模式之 解释器模式 Java实例代码演示
查看>>
设计模式之迭代器模式
查看>>
设计模式之空对象模式详解 附Java源码实例
查看>>
设计模式之访问者模式
查看>>
设计模式之享元模式
查看>>
Java代码设计模式讲解二十三种设计模式
查看>>
IDEA的使用教程二:idea的内置快捷键和idea的代码模板设置
查看>>
idea使用教程三:(创建web项目)
查看>>