NSURLProtocol 的使用和封装

NSURLProtocol的官方定义。
[quote]
An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.
[/quote]
[url]https://developer.apple.com/reference/foundation/nsurlprotocol[/url]


其实NSURLProtocol这个东西的作用就是让我们在app的内部拦截一切url请求(注意,不只是webView内的请求,而是整个app内的所有请求),如果筛选出来自己感兴趣的东西去处理,不感兴趣的就放过去就是了。既然能拦截,那么我们至少能做两件事,第一是拦截现有的url请求,比如常用的http://。第二就是我们可以自定义url协议了,比如boris://

这个东西完全可以取代以前大家常用的js和navtive通信的JSBridge。


我们现在尝试做一个和h5的通信。
刚才说到了,任何的app内部的url请求都会被拦截,当然也包含了webView发出的http请求。假设我们和h5约定一个规则,带有特殊参数"_mobile_bridge=1"的请求,是需要native处理的。那么我们就需要拦截到_mobile_bridge=1这个关键字。

PS:这样比传统的JSBridge的好处是,如果h5是在其他浏览器打开的,_mobile_bridge=1并不会起到任何作用,因此h5可以在没有被拦截的时候继续请求,只有在我们的app被拦截才会被app处理,并且NSURLProtocol还可以返回response给发起者,那么对h5来说,他们调用我们native的功能,就像发了一个请求一样简单。这样对h5开发的同学来说非常简单。JSBridge则比较麻烦。

我们需要继承NSURLProtocol并实现:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//如果请求已经被处理了,则不再重复处理
if ([NSURLProtocol propertyForKey:kBOURLProtocolHandled inRequest:request])
{
return NO;
}

//拦截我们约定好的规则
if([request.URL.absoluteString containsString:@"_mobile_bridge=1"])
{
return YES;
}

return NO;
}



拦截到我们想要处理的请求之后,可以通过- (void)startLoading方法来做任何想做的处理,比如把请求的url换一下,然后再重新请求。这些基本用法有很多文章在介绍,这里就不写来。现在来封装一下,让整个自定义协议功能对其他人来说变得更简单易用。

假设我们的需求是根据_mobile_bridge=1拦截之后,再根据url的path来决定做什么操作,比如/login就弹出native的登陆框 /share 就弹出分享框 成功登陆或分享后再返回一个response给h5。这样我们可以通过path来决定不同操作,可是不同的path可能是不同的开发者实现的,即使是一个人写的,我们也应该把所有path和对应操作独立出来以解耦。所有我们可以把path当作一个key,操作当作一个handler。

因此我们可以这样定义我们的URLProtocol:



//
// BOURLProtocol.h
// OSBuyApp
//
// Created by Boris on 17/2/28.
// Copyright © 2017年 Gome. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "BOURLProtocolResponse.h"

typedef void (^BOURLProtocolBLock)(BOURLProtocolResponse *response);

@protocol BOURLProtocolHandleProtocol <NSObject>

/**
处理完也许,创建一个BOURLProtocolResponse,并调用block

@param request 请求
@param block 回调
*/
- (void)handleURLProtocolRequest:(NSURLRequest *)request block:(BOURLProtocolBLock)block;

@end

@interface BOURLProtocol : NSURLProtocol

/**
注册处理器到协议
最好在应用启动的时候注册

每一次请求,都会根据path对应的Class来创建一个[新的实例]
因此建议不要和其他无关逻辑混在一起
调用实例的BOURLProtocolHandleProtocol方法做后续处理

@param handlerClass 实现了处理协议的Class
@param path url中的path,如http://baidu.com/image/test?a=1 中的 /image/test
*/
+ (void)registerHandler:(Class<BOURLProtocolHandleProtocol>)handlerClass
path:(NSString *)path;

/**
解除path的处理器

@param path path
*/
+ (void)unregisterHandlerWithPath:(NSString *)path;

@end



BOURLProtocolHandleProtocol协议规定了需要使用者实现request的处理以及通过block返回response。
+ (void)registerHandler:(Class<BOURLProtocolHandleProtocol>)handlerClass
path:(NSString *)path;
这个注册方法支持了每一个path都可以注册一个自己的handler。

实现如下:


static NSMutableDictionary *_handlerMap;

@interface BOURLProtocol()
@property(nonatomic,strong) id<BOURLProtocolHandleProtocol> handler;
@end

@implementation BOURLProtocol
+(void)initialize
{
if(self == BOURLProtocol.class)
{
_handlerMap = [NSMutableDictionary dictionary];
}
}
#pragma mark - Public

+ (void)registerHandler:(Class<BOURLProtocolHandleProtocol>)handlerClass
path:(NSString *)path
{
@synchronized (_handlerMap)
{
[_handlerMap setObject:handlerClass forKey:path];
}
}

+ (void)unregisterHandlerWithPath:(NSString *)path
{
@synchronized (_handlerMap)
{
[_handlerMap removeObjectForKey:path];
}
}


上边逻辑很简单,只是有一个静态的map来存储注册进来的path和handler的Class。

重点就要看刚才说到的startLoading,这个方法是处理拦截到的请求的。

/**
在该方法里做path的判断和对应的逻辑
*/
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
[BOURLProtocol setProperty:@(YES)
forKey:kBOURLProtocolHandled
inRequest:mutableReqeust];

self.handler = nil;
@synchronized (_handlerMap)
{
for(NSString *key in [_handlerMap allKeys])
{
if([mutableReqeust.URL.path isEqualToString:key])
{
Class class = [_handlerMap objectForKey:key];
self.handler = [[class alloc]init];
break;
}
}
}
BOURLProtocolResponse *protocolResponse = nil;

if(![self.handler respondsToSelector:@selector(handleURLProtocolRequest:block:)])
{
protocolResponse = [[BOURLProtocolResponse alloc]init];
protocolResponse.code = 400;
protocolResponse.errorMessage = @"No response";
[self handleProtocolResponse:protocolResponse];
}
else
{
[self.handler handleURLProtocolRequest:mutableReqeust
block:^(BOURLProtocolResponse *response)
{
[self handleProtocolResponse:response];
}];
}
}

- (void)handleProtocolResponse:(BOURLProtocolResponse *)protocolResponse
{
NSString *str = [protocolResponse jsonString];

NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary * headerFields = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%ld",data.length], @"Content-Length", @"text/json",@"Content-Type", nil];
NSHTTPURLResponse * response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headerFields];//statusCode == 200


[self.client URLProtocol:self
didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];

[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];

self.handler = 0;
}



上边的代码,先从_handlerMap找到path对应的Class,然后创建一个Class对应的Instance,然后调用Instance的handle方法,handle方法做完对应的操作,再返回response。
下边我们实现一个处理h5发来的alert请求的handler:


#import "TestProtocolHandler.h"
#import "AppDelegate.h"

@implementation TestProtocolHandler

- (void)handleURLProtocolRequest:(NSURLRequest *)request block:(BOURLProtocolBLock)block
{
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
BOURLProtocolResponse *response = [BOURLProtocolResponse alloc]nit];
response.data = @{@"result":@"cancel"};
response.code = 200;
response.errorMessage = @"";
block(response);
}];

UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
BOURLProtocolResponse *response = [[BOURLProtocolResponse alloc]init];
response.data = @{@"result":@"ok"};
response.code = 200;
response.errorMessage = @"";
block(response);
}];

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"BOURLProtocolDemo" message:@"BOURLProtocolDemo" preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:cancelAction];

[alert addAction:okAction];

UIViewController *vc = [[[[UIApplication sharedApplication ] delegate] window]rootViewController];

[vc presentViewController:alert animated:YES completion:nil];
}

@end



上边代码实现了alert的处理,弹出一个alert,根据用户点击的按钮来返回不同的response。

现在都实现好了,我们需要把这些东西注册到App中。这个非常简单:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

[NSURLProtocol registerClass:[BOURLProtocol class]];

[BOURLProtocol registerHandler:[TestProtocolHandler class] path:@"/alert"];

return YES;
}



上边第一行是在app里注册我们自定义的协议,这样才能拦截到请求。第二行是像我们自己的协议里注册path和处理器。
这样,不同业务组的不同开发者就可以单独注册自己的handler了。

demo地址在这里,[url]https://github.com/82934162/BOURLProtocolDemo[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值