`

NSURLProtocol 的使用和封装

阅读更多
NSURLProtocol的官方定义。
引用

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.

https://developer.apple.com/reference/foundation/nsurlprotocol

  
  其实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]init];
        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地址在这里,https://github.com/82934162/BOURLProtocolDemo





 
1
1
分享到:
评论

相关推荐

    Android实战——Retrofit2的使用和封装

    Android实战——Retrofit2的使用和封装

    使用C++封装的OpenGL.zip

    使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的OpenGL.zip使用C++封装的...

    变压器封装和立创标准封装库.zip

    在电子设计领域,电路板设计(PCB Design)是至关重要的环节,而元器件的封装则是PCB设计的基础。...在使用这些资源时,设计师应确保封装的适用性和准确性,并积极参与到社区的交流和共享中,以不断完善和扩展封装库。

    QFN贴片芯片封装(三维PCB封装库)AD用PCB封装库

    "注册方法.txt"文件很可能是提供给用户关于如何在Altium Designer中使用这些封装库的指导,或者可能是作者提供的许可信息,包括如何合法地使用和分发这些资源。遵循其中的指导,用户可以顺利地将这些封装集成到自己...

    常用封装vs最全封装

    同时,合理管理和维护封装库也是提升设计效率的重要环节,避免使用不正确或不完整的封装导致设计错误。 总的来说,"常用封装"和"最全封装"是Protel PCB设计中的重要资源,它们为工程师提供了丰富的元件模型,方便...

    Altium Designer PCB封装库大全pcb封装库

    在电子设计领域,Altium Designer是一款广泛使用的PCB(印制电路板)...了解和熟练使用封装库,尤其是3D建模和单片机封装,能够提升设计质量和工程效率。同时,掌握封装库的管理和自定义也是提升专业技能的重要途径。

    XH2.54封装(三维PCB封装库)AD用PCB封装库

    在电子设计领域,PCB(Printed Circuit Board)封装库是至关重要的资源,它包含了电路板上各种电子元件的形状、尺寸以及焊盘位置等信息。...合理使用和维护封装库,是提高PCB设计效率和质量的关键步骤。

    Altium Designer PCB封装库【很全】.zip_AOn的pcb封装_PCB封装_ad17封装库_altium pc

    PC104封装库则包含了符合这种接口标准的元件封装,允许设计师在设计PC104兼容的板子时方便地选择和使用这些封装。 压缩包内的文件"Altium Designer PCB封装库【很全】"很可能是包含了所有这些封装的单一文件或一个...

    跳线封装(三维PCB封装库)AD用PCB封装库

    标题中的“跳线封装(三维PCB封装库)AD用PCB封装库”是指在电子设计自动化(EDA)软件Altium Designer(简称AD)中使用的专门针对跳线的三维PCB封装库。这个库包含了设计师精心制作的跳线组件模型,以供用户在PCB...

    SMD表贴器件 Altium封装 AD封装库 2D+3D PCB封装库-17M.zip

    在进行PCB设计时,正确选择和使用SMD封装至关重要。错误的封装可能导致焊接问题、空间冲突,甚至影响电路功能。因此,这个压缩包提供的资源对于电子工程师和爱好者来说是一份宝贵的工具,可以帮助他们高效地完成PCB...

    ESOP8 ESOP16封装PCB封装库(AD库,封装带3D视图).zip

    在使用这个ESOP8和ESOP16封装库时,设计师需要在Altium Designer中导入.PcbLib文件。导入后,这些封装将出现在库管理器中,可以方便地被选择并添加到电路板设计中。通过精确地放置和连接这些封装,设计师可以确保...

    ESOP封装(三维PCB封装库)AD用PCB封装库

    在下载和使用这些封装时,应注意尊重作者的辛勤工作,自用而不应随意传播。这样既能保护原创者的权益,也能保证资源的质量和更新。 在实际操作中,导入AD的PCB封装库步骤如下: 1. 首先,解压下载的"ESOP.PcbLib...

    音频接口 Altium封装 AD封装库 2D+3D PCB封装库-15MB.zip

    "PCB封装列表.txt"文件则可能是对库中所有封装的一个文本列表,列出了每个封装的名称、描述或者关键属性,方便设计师快速查找和定位需要的封装。这种列表通常包括封装的名称、引脚数量、封装类型等信息,为设计过程...

    XH2.54封装(三维PCB封装库)AD用PCB封装库.zip

    《XH2.54封装:三维PCB封装库在AD中的应用》 在电子设计领域,PCB(Printed Circuit Board,印制电路板)的设计是至关重要的环节,而元器件封装...同时,我们也要尊重作者的辛勤付出,合理使用和分享这些宝贵的资源。

    MX1.25封装(三维PCB封装库)AD用PCB封装库

    在电子设计领域,PCB(Printed Circuit Board)封装库是至关重要的资源,它包含了电路元件的物理模型,用于在电路板布局时精确地表示各个元器件...通过合理使用和维护封装库,设计师可以更好地应对复杂的电子设计挑战。

    cadence allegro封装库封装大全

    通过“cadence allegro封装库封装大全”,用户不仅可以获取大量的预设封装,还能学习如何有效地管理和使用封装库,提升PCB设计的专业性和精确性。这个资源对于初学者和经验丰富的设计师来说都是宝贵的参考资料,可以...

    Android实战——okhttp的使用和封装

    Android实战——okhttp的使用和封装

    H264 裸流使用ffmepg封装MP4

    使用ffmpeg经H264裸码流封装为MP4文件。 使用ffmpeg经H264裸码流封装为MP4文件。

    系统封装首席执行官SC封装2.0+工具

    8. **更新与维护**:工具可能包含自动更新功能,确保系统封装过程使用的都是最新的补丁和软件版本,以减少安全漏洞。 9. **日志记录**:为了便于问题排查和日后的优化,SC封装工具可能会记录详细的执行日志,记录每...

    各种晶振封装AD封装库晶振

    晶振封装是晶振在电路板上安装和使用的物理形式,对于设计者来说,理解不同封装类型及其应用至关重要。标题提到的“AD封装库晶振”是指适用于Altium Designer(AD)这款电子设计自动化软件的晶振模型库。 Altium ...

Global site tag (gtag.js) - Google Analytics