该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
第11章 Foundation框架中的系统服务
11.6 URL处理
URL处理类用于通过标准Internet协议(ftp、http、https和本地文件),与URL进行交互和与数据资源进行通信;
这些类提供了下列功能:
1)URL加载;
2)缓存管理;
3)身份验证和证书管理;
4)Cookie管理;
5)协议支持;
这些类还支持代理服务器和每个用户的系统首选项安全套接字(SOCKS)网关;
11.6.1 URL加载
NSURL类:
NSURL对象用于管理URL和URL引用的资源;
NSURL类的方法可以用来创建和初始化NSURL对象、查询URL以及访问URL组件(即主机和端口等);
NSURL类还提供了用于处理书签数据的方法;
书签:
是指对文件位置的永久引用;它是一种宝贵的文件定位工具,因为即使在文件被移动或重命名的情况下,也可以使用它重建文件的URL;
NSURL * url = [NSURL URLWithString:@"http://www.apress.com:80"];
NSLog(@"URL: scheme %@,host %@,port %@",url.scheme,url.host,url.port);
log:
2017-12-26 10:53:01.911834+0800 精通Objective-C[26601:8389700] URL: scheme http,host www.apress.com,port 80
NSURLRequest类和NSMutableURLRequest类:
用于代表URL加载请求;
NSURLRequest类:
是一个独立的网络协议和URL方案;
使用其方法可以创建和初始化URL请求以及获取请求的属性;
NSMutableURLRequest类是NSURLRequest类的子类:
使用其方法可以更改URL及其组件;
示例:创建一个可变URL请求,然后设置该HTTP请求的方法值;
NSMutableURLRequest * req = [NSMutableURLRequest requestWithURL:url];
[req setHTTPMethod:@"GET"];
NSURLResponse类和NSHTTPURLResponse类:
用于代表URL请求反应的回应;
NSURLResponse对象可以有执行URL请求的同步加载操作(sendSynchronousRequest:returningResponse:error:)的对象创建;
也可以由遵守NSURLConnectionDataDelegate协议的对象创建;
NSHTTPURLResponse是NSURLResponse类的子类:
使用其方法可以访问由URL请求返回的HTTP特定信息;
NSUELConnection类和NSURLDownload类:
用于下载通过URL识别的资源;
NSUELConnection类支持同步和异步方式的资源加载、开始和停止连接,以及管理用于控制URL连接各个方面(即缓存管理、身份认证和证书、协议支持和Cookie的管理)的委托对象;
在使用异步加载资源时必须设置委托对象:
该对象遵循NSURLConnectionDelegate协议,至少会实现如下方法:
1)connection:didReceiveData:(获取被加载的数据)
2)connection:didFailWithError:(处理链接错误)
3)connection:didFinishLoading:(加载数据操作完成)
示例:使用NSURLConnection类以同步的方式加载URL的内容:
NSURL * url1 = [NSURL URLWithString:@"http://www.apress.com/index.html"];
NSURLRequest * request1 = [NSURLRequest requestWithURL:url1];
NSURLResponse * response1;
NSError * aerror1;
NSData * data1 = [NSURLConnection sendSynchronousRequest:request1 returningResponse:&response1 error:&aerror1];
NSLog(@"Expected content length = %lld,loaded %lu bytes",[response1 expectedContentLength],[data1 length]);
log:
2017-12-26 11:25:39.451971+0800 精通Objective-C[26911:8415701] Expected content length = -1,loaded 11941 bytes
注:ios9之后 推荐使用NSURLSession类;
NSURLDownload类:
用于通过异步方式,将URL的内容下载到文件中;
使用其方法可以初始化下载操作(设置请求和委托对象)、设置目标路径、重新开始未完成的下载操作和取消下载请求;
NSURLDownload类还提供了对MacBinary、BinHex和gzip格式文件的支持;
其委托对象必须遵守NSURLDownloadDelegate协议,因而会至少实现下列方法:
1)download:didFailWithError:(处理链接错误)
2)downloadDidFinish:(加载数据完成后,执行处理操作)
注:这个类只能在Max OX上使用,现在的话 推荐使用NSURLSessionDownloadTask类,可自行查阅API;
11.6.2 缓存管理
缓存管理类(NSURLCache和NSCachedURLResponse)为对URL请求的回应提供了缓存;
NSURLCache类为NSURL提供了通用缓存;
NSCachedURLResponse类用于封装URL回应(即NSURLResponse对象)和通用URL加载的数据(即NSData对象);
NSURLConnection委托对象必须实现connection:willCacheResponse:方法,提供由NSURLCache实例初始化的NSCachedURLResponse对象,以便管理缓存
接下里,我们将编写一个用URL加载类下载资源的程序;
11.6.3 使用URL加载API下载资源
下面编写使用URL加载API下载URL资源的类:C11NetConnector;
(Code C11NetConnector.h C11NetConnector.m)
#import <Foundation/Foundation.h>
#define HTTP_SCHEME @"http"
#define HTTPS_SCHEME @"https"
#define CACHE_MEMARY_SIZE (4*1024*1024)
@interface C11NetConnector : NSObject<NSURLConnectionDelegate>//接口采用NSURLConnectionDelegate协议的代理,因而能够通过异步方式加载来自NSURLConnection实例的数据
@property (readonly) BOOL finishedLoading;//表示 是否结束了加载数据的操作
-(id)initWithRequest:(NSURLRequest *)request;
-(void)reloadRequest;//重新加载NSURLRequest对象
@end
#import "C11NetConnector.h"
//通过扩展声明,提供属性
@interface C11NetConnector()<NSURLConnectionDataDelegate>
@property NSURLRequest * request;
@property BOOL finishedLoading;
@property NSURLConnection * connector;
@property NSMutableData * receiveData;
@end;
@implementation C11NetConnector
-(id)initWithRequest:(NSURLRequest *)request{
if (self = [super init]) {
_request = request;
//通过合适的内存存储结构,创建URL缓存
NSURLCache * URLCache = [[NSURLCache alloc] init];
[URLCache setMemoryCapacity:CACHE_MEMARY_SIZE];
[NSURLCache setSharedURLCache:URLCache];
//创建连接并开始下载资源数据
_connector = [NSURLConnection connectionWithRequest:request delegate:self];
}
return self;
}
-(void)reloadRequest{
self.finishedLoading = NO;
self.connector = [NSURLConnection connectionWithRequest:self.request delegate:self];
}
#pragma mark -
#pragma mark Delegate methods
//当连接能够对请求做出回应时,会发送该消息
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
if (self.receiveData != nil) {
//将receiveData对象长度置0,从而丢弃了通过上一个请求接收的数据
[self.receiveData setLength:0];
}
}
//在连接将回应存储到缓存前,会发送该消息
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse{
//使用这个方法能够修改该回应,也可以阻止它被存储到缓存中
NSURLResponse * response = [cachedResponse response];
NSURL * url = [response URL];
if ([[url scheme] isEqualToString:HTTP_SCHEME] || [[url scheme] isEqualToString:HTTPS_SCHEME]) {
NSLog(@"Downloaded data,caching response");
return cachedResponse;
}else{
NSLog(@"Downloaded data,not caching response");
return nil;
}
}
//接收资源时,,会发送该消息;若以递增的方式接收数据,在单个请求中该方法可能会被调用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (self.receiveData != nil) {
[self.receiveData appendData:data];
}else{
self.receiveData = [[NSMutableData alloc] initWithData:data];
}
}
//当连接成功完成加载数据操作时,会发送该消息
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSUInteger length = [self.receiveData length];
NSLog(@"Download %lu bytes from request %@",length,self.request);
//加载数据后,设置退出运行循环的标记
self.finishedLoading = YES;
}
//当连接加载数据失败时,会发送该消息
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"Error loading request %@",[error localizedDescription]);
self.finishedLoading = YES;
}
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
//使用用户名 密码 创建了一个证书
NSURLCredential * credential = [NSURLCredential credentialWithUser:@"TestUser" password:@"TestPassword" persistence:NSURLCredentialPersistenceForSession];
//使用证书回应连接的身份认证质询
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
@end
我们来详细看一下这段代码:
1)使用分类声明了C11NetConnector类的实现代码中使用的私有属性;
C11NetConnector的实现代码定义了接口声明的方法和NSURLConnectionDataDelegate协议声明方法;
init方法中,为了缓存回应,创建并初始化了一个NSURLCache实例;
创建缓存,并使用内存存储结构配置了该缓存,然后将其设置为与连接一起使用的共享缓存;
最后创建了一个NSURLConnection对象并开始下载URL;
2)其余代理方法,我们在代码中做了注释;
编写好这个类之后,我们来使用它加载一个URL;
NSString * index_url = @"http://www.wikipedia.com/index.html";
//获取当前的运行循环
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
//使用指定的缓存策略来创建请求,然后开始下载
NSURLRequest * request2 = [NSURLRequest requestWithURL:[NSURL URLWithString:index_url] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:5];
C11NetConnector * netConnect = [[C11NetConnector alloc] initWithRequest:request2];
//一直循环 知道加载资源操作结束为止
while (!netConnect.finishedLoading && [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
//记录缓存占用内存大小
NSLog(@"Cache memory usage = %lu bytes",[[NSURLCache sharedURLCache] currentMemoryUsage]);
//通过请求重新加载数据,这次会从缓存获取数据
[netConnect reloadRequest];
while (!netConnect.finishedLoading && [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
//运行循环,直到netConnect.finishedLoading == YES;
}
//释放缓存
[[NSURLCache sharedURLCache] removeAllCachedResponses];
log:
2017-12-26 16:05:35.861566+0800 精通Objective-C[29350:8632734] Downloaded data,caching response
2017-12-26 16:05:35.861917+0800 精通Objective-C[29350:8632734] Download 1242 bytes from request <NSURLRequest: 0x60c000012d70> { URL: http://www.wikipedia.com/index.html }
2017-12-26 16:05:35.862149+0800 精通Objective-C[29350:8632734] Cache memory usage = 1242 bytes
2017-12-26 16:05:36.059240+0800 精通Objective-C[29350:8632734] Download 1242 bytes from request <NSURLRequest: 0x60c000012d70> { URL: http://www.wikipedia.com/index.html }
分析:
1)NSURLRequestReturnCacheDataElseLoad对应的缓存策略设置了只有在cache中不存在data时才从原始地址下载;
注意,如果某个url不支持缓存策略,即便设置了也是无效的;
2) while (!netConnect.finishedLoading && [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
这段代码可能会觉得熟悉,是的我们之前用过:
这段代码会运行循环,从输入源接收事件并执行相应的委托方法,直到连接完成加载资源的操作为止;
3)我们将缓存使用情况打印出来;
重新请求时,因为数据已经被存储在缓存中,而且缓存策略设置了应使用的缓存大小,所以无须从URL加载数据就可以立即得到数据;
从缓存中获取数据,connection: willCacheResponse:委托方法将不会被调用;
4)最后释放缓存;
这个程序实践了使用URL加载API,通过异步方式下载URL的知识;
备注:
1.NSURLRequestUseProtocolCachePolicy NSURLRequest 默认的cache policy,使用Protocol协议定义。
2.NSURLRequestReloadIgnoringCacheData 忽略缓存直接从原始地址下载。
3.NSURLRequestReturnCacheDataDontLoad 只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式
4.NSURLRequestReturnCacheDataElseLoad 只有在cache中不存在data时才从原始地址下载。
5.NSURLRequestReloadIgnoringLocalAndRemoteCacheData 忽略本地和远程的缓存数据,直接从原始地址下载,与NSURLRequestReloadIgnoringCacheData类似。
6.NSURLRequestReloadRevalidatingCacheData 验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据
下面介绍下载资源是解决身份验证的手段;
11.6.4 身份认证和证书管理
身份认证和证书管理类:
NSURLProtectionSpace、NSURLCredentialStorage、NSURLCredential、NSURLAuthenticationChallenge和NSURLAuthenticationChallengeSender;
提供了对请求访问受保护URL的用户进行身份认证;
需要身份认证的资源要求 尝试加载资源的客户端提供证书;
Foundation框架的NSURLAuthenticationChallenge类封装了从客户端向服务端提供认证请求的功能;
NSURLCredential类代表为了回应身份认证要求,用户返回的证书;
当连接请求要求进行身份认证时,NSURLConnection和NSURLDownload实例的委托协议就是被发送的消息;
这样就会实现相应的方法,返回正确的证书;
对于NSURLConnectionDelegate协议来说:
应实现connection:willSendRequestForAuthenticationChallenge:方法;
我们可以为C11NetConnector类实现这个方法;
(Code)
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
//使用用户名 密码 创建了一个证书
NSURLCredential * credential = [NSURLCredential credentialWithUser:@"TestUser" password:@"TestPassword" persistence:NSURLCredentialPersistenceForSession];
//使用证书回应连接的身份认证质询
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
11.6.5 Cookie管理
使用Foundation框架的NSHTTPCookie和NSHTTPCookieStorage类可以创建和管理HTTP Cookie;
这类Cookie用于永久存储跨URL请求的数据;
一个NSHTTPCookie实例就代表一个Cookie;
NSHTTPCookieStorage是一个单例对象,用于管理Cookie;
对于OS X Cookie是公用的,并且由多个进程以同步方式访问;
对于单个进程来说,会话Cookie是局部的;不会与其他程序共享;
NSHTTPCookie提供的方法:
可以创建Cookie、将Cookie转换为请求的报头和 获取Cookie属性;
NSHTTPCookieStorage提供的方法:
使用可以回去公用Cookie的存储实例、管理Cookie接受策略和管理(即添加、删除和获取)Cookie;
11.6.6 协议支持
使用Foundation框架的NSURLProtocol类和NSURLProtocolClient类;
可以从url加载数据创建自定义协议;
NSURLProtocol类:
是一个抽象类,提供了执行协议特定URL加载的基础结构;
使用其方法可以创建NSURLProtocol对象、注册协议类和取消协议类的注册、管理请求、获取协议特性以及开始和停止下载操作;
NSURLProtocolClient类:
是一个由NSURLProtocol类的子类使用的协议,用于与URL加载系统通信;
11.7 进程间通信
Foundation框架提供了许多用于进程间通信的类;
尤其可以创建和使用通信信道;
11.7.1 通过信道通信
NSPipe类可以封装管道(pipe);
管道是用于在进程间进行通信的单向信道;
使用其方法可以创建和初始化管道,以及获取管道的想用NSFileHandle实例;
NSTask类(进程)提供了两个方法:
setStandardOutput:和setStandardInput:,用于设置进程的输入和输出信道;
示例:使用NSPipe和NSTask类,创建并启动一个任务,设置标准输出,然后在任务完成后获取该标准输出并将之写入信道
(Unix命令/bin/ls仅用于列出当前目录中的文件)
(Code_11.7.1)
NSTask * task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/ls"];
NSPipe * outPipe = [NSPipe pipe];
[task setStandardOutput:outPipe];
[task launch];
NSData * output = [[outPipe fileHandleForReading] readDataToEndOfFile];
NSString * lsout = [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding];
NSLog(@"/bin/ls output:\n%@",lsout);
log:(由于这个类只能在OS X上使用,所以我们在C11BonjourClient工程中的main函数中,实现如上测试代码)
C11BonjourClient[30348:9164991] /bin/ls output:
C11BonjourClient
11.7.2 通过端口通信
NSPort、NSMachPort、MSMessagePort和NSSocketPort类:
为进程和线程间通信提供了底层机制(通常通过NSPortMessage对象);
NSPort类:
是一个抽象类;使用其方法可以创建和初始化端口、创建端口连接、设置端口信息和监听端口;
NSMachPort、MSMessagePort和NSSocketPort类:
是NSPort类的具体子类,用于设置通信端口的类型;
NSMachPort、MSMessagePort类:
仅允许进行本地通信(即在同一台设备上);
此外,NSMachPort类用于Mach端口(OS X中的基础通信端口);
NSSocketPort类:
即允许进行本地通信,也允许进行远程通信;(用作本地通信时效率不如专门用于本地通信的类)
在创建NSConnection实例时(使用initWithReceivePort:sendPort:方法),可以将端口实例用作参数;
还可以通过NSPort类的scheduleInRunLoop:forMode:方法,向运行循环中添加端口;
因为端口是底层的进程间通信机制,所以在实现应用通信时,应在尽可能地使用分布式对象,而只在必要时使用NSPort对象;
结束对端口对象的操作后,必须使用NSPort类的invalidate方法显示地使对象失效;
11.7.3 端口注册
NSPortNameServer、NSMachBootstrapServer、NSMessagePortNameServer和NSSocketPortNameServer类为端口注册服务提供了接口;
使用它们可以获取NSMachPort、NSMessagePort和NSSocketPort实例;
NSPortNameServer类为分布式对象系统使用的端口注册服务提供了面向对象的接口;
NSConnection对象使用该接口相互联系,并将对象分发到网络中;
直接通过NSPortNameServer实例进行交互的情况极少见;
NSMachBootstrapServer、NSMessagePortNameServer和NSSocketPortNameServer类:
是NSPortNameServer类的子类;
它们可以返回相应的端口实例(NSMachPort、MSMessagePort和NSSocketPort);
11.8 小结
本章介绍Foundation框架,专注于为大多数OC程序提供通用功能的类;
现在熟悉这些类提供的如下功能即可:
1)网络服务;
2)应用服务;
3)文件系统服务;
4)URL处理;
5)进程间通信;
6)并发机制和线程;