NSURL
先来聊聊NSURL吧,NSURL实际上就是对网址字符串的一个封装。而之所以进行这个封装,就是因为请求网址字符串中包括协议类型、服务器地址、端口号、资源层级、文件名等等(这些项目是按照一定的规则组合在一起的),我们要在字符串中获取到其中某一项,那就要自己写一个正则表达式来获取到,这是很不方便的;而NSURL就可以将这个网址字符串分解成固定特有的子字符串,这样就方便了系统的调用以及开发人员的debug。
如下图所示:
如果在网址字符串中,我要找到服务器地址或者是端口号或者是协议类型,那么我就要写大量的正则表达式来进行匹配。而将其封装成NSURL之后,就很方便了,如下都是NSURL的属性:
代码语言:javascript复制@property (nullable, readonly, copy) NSString *absoluteString;
@property (readonly, copy) NSString *relativeString; // The relative portion of a URL. If baseURL is nil, or if the receiver is itself absolute, this is the same as absoluteString
@property (nullable, readonly, copy) NSURL *baseURL; // may be nil.
@property (nullable, readonly, copy) NSURL *absoluteURL; // if the receiver is itself absolute, this will return self.
/* Any URL is composed of these two basic pieces. The full URL would be the concatenation of [myURL scheme], ':', [myURL resourceSpecifier]
*/
@property (nullable, readonly, copy) NSString *scheme;
@property (nullable, readonly, copy) NSString *resourceSpecifier;
/* If the URL conforms to rfc 1808 (the most common form of URL), the following accessors will return the various components; otherwise they return nil. The litmus test for conformance is as recommended in RFC 1808 - whether the first two characters of resourceSpecifier is @"//". In all cases, they return the component's value after resolving the receiver against its base URL.
*/
@property (nullable, readonly, copy) NSString *host;
@property (nullable, readonly, copy) NSNumber *port;
@property (nullable, readonly, copy) NSString *user;
@property (nullable, readonly, copy) NSString *password;
@property (nullable, readonly, copy) NSString *path;
@property (nullable, readonly, copy) NSString *fragment;
@property (nullable, readonly, copy) NSString *parameterString;
@property (nullable, readonly, copy) NSString *query;
@property (nullable, readonly, copy) NSString *relativePath; // The same as path if baseURL is nil
URL,全称是Uniform Resource Locator的缩写,即“统一资源定位符”,因此它既可以表示网络资源的路径,也可以表示本地资源的路径。
本地文件的路径要在资源路径前面加上file://,所以,当我们根据资源路径字符串来创建一个本地文件的URL的时候,需要使用fileURLWithPath方法,而不是使用URLWithString。如下所示:
NSURLRequest
NSURLRequest实际上是对NSURL进一步的包装。
一个NSURL实际上就代表了一个网络请求的地址,而一个Http请求,除了包含网络请求的地址,还需要包含请求方法、协议版本、编码类型等信息,而这些信息是简简单单的一个NSURL所携带不了的,所以需要对NSURL进行一个封装,封装成NSURLRequest。
在APP中,任何与网络相关的操作,都是通过NSURLRequest进行的。比如WebView加载网页:
代码语言:javascript复制[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"blabla"]]];
NSURLSession
总的来说,NSURLSession就是负责网络请求的接收、发送以及处理,用大白话讲就是,我们传入一个request,session将该request进行各种处理。
我们将一个浏览器理解成是一个session,一个session里面是可以创建多个网络请求,这可以类比成,一个浏览器可以打开多个网页。
代码语言:javascript复制NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *urlsession = [NSURLSession sessionWithConfiguration:configuration];
如上面代码所示,我们创建session的时候,是需要传入一个configuration配置对象,这个配置对象可以配置session的各种策略,比如Cache策略、cookie策略等。在APP中,我们可以创建不同配置的多个session。浏览器中有无痕模式和普通模式浏览,这两种模式实际上就是两种不同配置的session。
那么session是如何处理request的呢?session会将request封装成一个NSURLSessionTask,然后通过task来控制网络请求的状态(比如开始、取消),以及监测网络请求的进度。如下图所示:
接下来我们再详细看一下NSURLSession的组织结构。
上面也说到了,session与session之间的不同之处在于其configuration的不同。我们可以通过NSURLSessionDelegate来监听网络请求的各个状态。
总的来说,我们可以传入一个configuration来创建一个session,代码如下:
代码语言:javascript复制@property (class, readonly, strong) NSURLSession *sharedSession;
(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(nullable id <NSURLSessionDelegate>)delegate
delegateQueue:(nullable NSOperationQueue *)queue;
然后通过session将NSURLRequest封装成一个NSURLSessionTask,根据task的用途不同,我们可以将task分为如下四种:
- dataTask:处理简单的数据流,如JSON数据;
- downloadTask:大数据下载,可实现断点续传,可以监听并显示下载进度;
- uploadTask:上传数据;
- streamTask:流数据。
通过task我们可以管理请求的状态以及监听进度;我们也可以通过NSURLSessionDelegate来监听请求的进程状态。
NSURLSessionTask
上文道,可利用NSURLSession对象将NSURLRequest对象封装成一个NSURLSessionTask,之所以要进行这一层封装,就是为了方便管理网络请求的状态以及监控其网络进程。
NSURLSessionTask的状态(state)有如下四种:
代码语言:javascript复制typedef NS_ENUM(NSInteger, NSURLSessionTaskState) {
NSURLSessionTaskStateRunning = 0, /* The task is currently being serviced by the session */
NSURLSessionTaskStateSuspended = 1,
NSURLSessionTaskStateCanceling = 2, /* The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message. */
NSURLSessionTaskStateCompleted = 3, /* The task has completed and the session will receive no more delegate notifications */
};
针对NSURLSessionTask的操作有如下三种:
代码语言:javascript复制- (void)suspend;//挂起
- (void)resume;//恢复
- (void)cancel;//取消
一开始生成一个NSURLSessionTask对象后,该sessionTask默认是挂起(NSURLSessionTaskStateSuspended)的状态,然后我们使用 resume 方法来开始执行该task。
那么我们该如何获取到网络请求的返回数据呢?如下图:
我们可以使用NSURLSession中封装request或者是URL的方法中的block回调来监听请求的最终状态并获取返回数据;也可以通过NSURLSessionTaskDelegate中的各个代理方法来监听网络请求的进程,并处理对应的返回数据。
接下来说一个题外话。在iOS9之后,苹果新增了一个ATS(APP Transport Security)特性,默认要求都是用https进行请求。所以在iOS9之后,如果我们程序中使用了Http的请求,那么苹果就会报错,此时我们要在info.plist文件中修改ATS的默认值,如下图所示:
iOS系统框架整个网络请求的流程如下:
- 将网址字符串封装成NSURL,然后将NSURL封装成NSURLRequest;
- 创建一个NSURLSessionConfiguration对象,并进行对应的个性化配置,然后将之传入生成一个NSURLSession;
- 使用NSURLSession对象将NSURLRequest对象封装成NSURLSessionTask对象;
- 通过对task进行resume、cancel等操作来控制网络请求的状态;
- 可以通过block handler或者代理来监听请求进程,并处理数据。
开源网络框架杂谈
上文聊了iOS系统网络请求框架的基本使用,我们了解到,对于一个网络请求,我们需要对其进行大量的设置,比如网络请求方式、数据可解析格式、缓存方式等等;而且网络请求成功以后的回调方式也不是特别友好,并且还需要自己解析JSON数据;而且好自己控制网络请求的开始、取消......所以,面对iOS系统网络框架进行网络请求的时候,如果我们的请求比较复杂,都会导致网络请求相关的代码急于增加,而且重复代码非常多。
所以,我们有必要使用开源网络框架来帮我们简化这些繁琐的操作。实际上,无论是哪种开源网络框架,都是对系统网络框架的二次封装。在众多开源网络框架中,我们最常使用的,也是最有名的是AFNetworking。
那么我们怎么样将开源框架集成到我们的项目中呢?接下来我详细介绍一下使用Cocoapods来管理第三方库的原理。
首先,在MyProject文件所在的文件夹下,建立一个Podfile文件;
然后,根据相关语法规则,去在Podfile中输入想要集成的代码;
第三步,执行pod install,此时,cocoapods就会根据Podfile的内容去下载相关第三方框架的代码,然后将这些下载好的代码组合生成一个名为Pods的project;
第四步,将Pods project和MyProject 组合,自动生成一个workspace。
那么,Cocoapods是如何找到这个第三方库的呢?
首先,我们自己写好一个公用组件,然后将这个组件托管到一个平台上,比如托管到GitHub上面;
然后在cocoaPods服务器中配置该公用组件,配置组件的名字,以及其下载地址;
这样当其他人使用Cocoapods安装我这个公用组件的时候,Cocoapods会通过名字在pods服务器中寻找是否有该名称的组件,如果有就通过地址下载该组件。
以上。