最近在项目里由于电信那边发生dns发生域名劫持,因此需要手动将URL请求的域名重定向到指定的IP地址,但是由于请求可能是通过NSURLConnection,NSURLSession或者AFNetworking等方式,因此要想统一进行处理,一开始是想通过Method Swizzling去hook cfnetworking底层方法,后来发现其实有个更好的方法–NSURLProtocol。

NSURLProtocol

NSURLProtocol能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。

使用场景

不管你是通过UIWebView, NSURLConnection 或者第三方库 (AFNetworking, MKNetworkKit等),他们都是基于NSURLConnection或者 NSURLSession实现的,因此你可以通过NSURLProtocol做自定义的操作。

  • 重定向网络请求

  • 忽略网络请求,使用本地缓存

  • 自定义网络请求的返回结果

  • 一些全局的网络请求设置

拦截网络请求

子类化NSURLProtocol并注册

@interface CustomURLProtocol : NSURLProtocol

@end

然后在application:didFinishLaunchingWithOptions:方法中注册该CustomURLProtocol,一旦注册完毕后,它就有机会来处理所有交付给URL Loading system的网络请求。

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

//注册protocol

[NSURLProtocol registerClass:[CustomURLProtocol class]];

return YES;

}

实现CustomURLProtocol

注册好了之后,现在可以开始实现NSURLProtocol的一些方法:

  • +canInitWithRequest:

    这个方法主要是说明你是否打算处理对应的request,如果不打算处理,返回NO,URL Loading System会使用系统默认的行为去处理;如果打算处理,返回YES,然后你就需要处理该请求的所有东西,包括获取请求数据并返回给 URL Loading System。网络数据可以简单的通过NSURLConnection去获取,而且每个NSURLProtocol对象都有一个NSURLProtocolClient实例,可以通过该client将获取到的数据返回给URL Loading System。

    这里有个需要注意的地方,想象一下,当你去加载一个URL资源的时候,URL Loading System会询问CustomURLProtocol是否能处理该请求,你返回YES,然后URL Loading System会创建一个CustomURLProtocol实例然后调用NSURLConnection去获取数据,然而这也会调用URL Loading System,而你在+canInitWithRequest:中又总是返回YES,这样URL Loading System又会创建一个CustomURLProtocol实例导致无限循环。我们应该保证每个request只被处理一次,可以通过+setProperty:forKey:inRequest:标示那些已经处理过的request,然后在+canInitWithRequest:中查询该request是否已经处理过了,如果是则返回NO。

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

//只处理http和https请求

NSString *scheme = [[request URL] scheme];

if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||

[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))

{

//看看是否已经处理过了,防止无限循环

if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {

return NO;

}

return YES;

}

return NO;

}

  • +canonicalRequestForRequest:

    通常该方法你可以简单的直接返回request,但也可以在这里修改request,比如添加header,修改host等,并返回一个新的request,这是一个抽象方法,子类必须实现。

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {

NSMutableURLRequest *mutableReqeust = [request mutableCopy];

mutableReqeust = [self redirectHostInRequset:mutableReqeust];

return mutableReqeust;

}

+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request

{

if ([request.URL host].length == 0) {

return request;

}

NSString *originUrlString = [request.URL absoluteString];

NSString *originHostString = [request.URL host];

NSRange hostRange = [originUrlString rangeOfString:originHostString];

if (hostRange.location == NSNotFound) {

return request;

}

//定向到bing搜索主页

NSString *ip = @"cn.bing.com";

// 替换域名

NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];

NSURL *url = [NSURL URLWithString:urlString];

request.URL = url;

return request;

}

  • +requestIsCacheEquivalent:toRequest:

    主要判断两个request是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现。

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b

{

return [super requestIsCacheEquivalent:a toRequest:b];

}

  • -startLoading -stopLoading

    这两个方法主要是开始和取消相应的request,而且需要标示那些已经处理过的request。

- (void)startLoading

{

NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];

//标示改request已经处理过了,防止无限循环

[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];

self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];

}

- (void)stopLoading

{

[self.connection cancel];

}

  • NSURLConnectionDataDelegate方法

    在处理网络请求的时候会调用到该代理方法,我们需要将收到的消息通过client返回给URL Loading System。

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

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

}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

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

}

- (void) connectionDidFinishLoading:(NSURLConnection *)connection {

[self.client URLProtocolDidFinishLoading:self];

}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

[self.client URLProtocol:self didFailWithError:error];

}

现在你已经可以截取request并做你想做的事了,这里有个demo:https://github.com/FreeMind-LJ/NSURLProtocolExample

可以参考一下,截取request并重新定向到新的地址,具体dns解析方法可以参看DNS解析(http://www.jianshu.com/p/d945454e3abc) ,如有不对,欢迎指正,哈~

iOS 开发之— NSURLProtocol的更多相关文章

  1. 李洪强iOS开发之-入门指南

    李洪强iOS开发之-入门指南 1零基础小白如何进行iOS系统学习 首先,学习目标要明确:其次,有了目标,要培养兴趣,经常给自己一些正面的反馈,比如对自己的进步进行鼓励,在前期小步快走:再次,学技术最重 ...

  2. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

  3. iOS开发系列--打造自己的“美图秀秀”

    --绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益于它强大的开发框架.今天我们将围绕iOS中两大图形.图像绘图框架进行介绍:Quartz ...

  4. iOS开发之再探多线程编程:Grand Central Dispatch详解

    Swift3.0相关代码已在github上更新.之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread.操作队列以及GCD,介绍的不够深入.今天就以GCD为主题来全面的总结一下GCD ...

  5. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  6. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  7. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

  8. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  9. iOS开发系列--通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总

    --系统应用与系统服务 iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用 ...

随机推荐

  1. 使用IIS6.0遇到问题后,常用的几种解决方法

    1.检查 .Net Framework,是否安装完全,不确定的情况下使用:aspnet_regiis.exe -i 或者 aspnet_regiis.exe -r 2.检查 IIS 6.0 其它相关配 ...

  2. POJ 1556 The Doors(线段交+最短路)

    The Doors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 5210   Accepted: 2124 Descrip ...

  3. c#通过操作mongodb gridfs实现文件的数据库存储

    @(编程) 源码 using MongoDB.Driver; using MongoDB.Driver.GridFS; using System.IO; namespace Wisdombud.Mon ...

  4. C++的双冒号(域解析符)

    在C++中,“::”表示“作用域标识符”或者叫“作用域分解运算符”,比如:“类名::函数名”,这样是表示该函数是该类的成员函数, 但是象下面这种写法:“::函数名”,作用域标识符前面没有任何对象,代表 ...

  5. 【Linux笔记】Linux目录结构

    [Linux笔记]Linux目录结构   本文内容整理自网络,以作参考. /:根目录,位于linux文件系统目录结构的顶层,一般根目录下只存放目录,不要存放文件,/etc./bin./dev./lib ...

  6. class str

    class str(object): """ str(object='') -> str str(bytes_or_buffer[, encoding[, erro ...

  7. UIView的autoresizingMask属性

    今天做相册列表的时候,发现有些 UITableViewController 属性不好记忆,然后就查找了一些资料.做一下备份. 在 UIView 中有一个autoresizingMask的属性,它对应的 ...

  8. Toast在关闭应用后还显示的解决办法

    1.我们在用Toast的用法就是:Toast.makeText(Context,CharSequence , Duration).show().但有的时候如果你在一次操作当中多次点击一个view的时候 ...

  9. VTK序列图像的读取[转][改]

    医学图像处理的应用程序中,经常会碰到读取一个序列图像的操作.比如CT.MR等所成的图像都是一个切面一个切面地存储的,医学图像处理程序要处理这些数据,第一步当然是把这些数据从磁盘等外部存储介质中导入内存 ...

  10. CloudStack服务引擎配置(cloud-engine-service模块)

    "?> <!--CloudStack服务引擎配置--> <beans xmlns="http://www.springframework.org/schem ...