美文网首页网络请求#iOS#HeminWon
iOS网络编程读书笔记

iOS网络编程读书笔记

作者: melouverrr | 来源:发表于2016-01-19 10:31 被阅读694次

    iOS网络编程读书笔记

    Facade Tester客户端门面模式的实例(被动版本化)

    被动版本化,所以硬编码URL.

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            FTAppDelegate *appDelegate = (FTAppDelegate*)[[UIApplication sharedApplication] delegate];
    
        if (appDelegate.urlForWeatherVersion1 != nil) {
            NSError *error = nil;
            NSData *data = [NSData dataWithContentsOfURL:appDelegate.urlForWeatherVersion1 
                                                 options:NSDataReadingUncached 
                                                   error:&error];
            
            if (error == nil) {
                NSDictionary *weatherDictionary = [NSJSONSerialization JSONObjectWithData:data 
                                                                                  options:NSJSONReadingMutableLeaves 
                                                                                    error:&error];
                
                if (error == nil) {
                    v1_city = [weatherDictionary objectForKey:@"city"];
                    v1_state = [weatherDictionary objectForKey:@"state"];
                    v1_temperature = [weatherDictionary objectForKey:@"currentTemperature"];
    
                    // update the table on the UI thread
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self.tableView reloadData];
                    });
                    
                } else {
                    NSLog(@"Unable to parse weather because of error: %@", error);
                    [self showParseError];
                }
    
            } else {
                [self showLoadError];
            }
            
        } else {
            [self showLoadError];
        }
    });
    }
    

    通过GCD在global线程中加载网络请求,用到的是[NSData dataWithContensOfURL:]; 数据用SJSONSerialization JSONObjectWithData:data..]进行序列化然后就能使用了。

    在应用委托中定义可以使得在实现服务定位器时更具有灵活性

    #import <UIKit/UIKit.h>
    
    @interface FTAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
    
    @property (strong, nonatomic) UIWindow              *window;
    @property (strong, nonatomic) UITabBarController    *tabBarController;
    
    // normally you wouldn't put these here!
    @property (strong, nonatomic) NSURL                 *urlForWeatherVersion1;
    @property (strong, nonatomic) NSURL                 *urlForWeatherVersion2;
    @property (strong, nonatomic) NSURL                 *urlForStockVersion1;
    @property (strong, nonatomic) NSURL                 *urlForStockVersion2;
    
    
    @end
    

    由于大多数Web Service 都会将结果以JSON的形式输出,因此使用JSON来表示服务定位器比较好。服务定位器用来探测天气与股票报价API端点。该结构将端点的所有版本都组合到了一个文件中。

    在应用启动和返回到前台时加载服务定位器,将URL存储为应用委托中的属性.(更复杂的应用则需要专门的网络管理器,由它处理服务定位器的加载,其他viewcontroller 也会使用它针对特定的网络调用查询端点(api))

    - (void)loadServiceLocator {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSError *error = nil;
            #warning Replace the following line with your own domain and path to the service locator
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://example.com/api/serviceLocator.json"] 
                                                 options:NSDataReadingUncached 
                                                   error:&error];
        
        if (error == nil) {
            NSDictionary *locatorDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
            
            if (error == nil) {
                self.urlForStockVersion1 = [self findURLForServiceNamed:@"stockQuote" version:1 inDictionary:locatorDictionary];
                self.urlForStockVersion2 = [self findURLForServiceNamed:@"stockQuote" version:2 inDictionary:locatorDictionary];
                self.urlForWeatherVersion1 = [self findURLForServiceNamed:@"weather" version:1 inDictionary:locatorDictionary];
                self.urlForWeatherVersion2 = [self findURLForServiceNamed:@"weather" version:2 inDictionary:locatorDictionary];
                
            } else {
                NSLog(@"Unable to parse service locator because of error: %@", error);
                
                // inform the user on the UI thread
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[[UIAlertView alloc] initWithTitle:@"Error" 
                                                message:@"Unable to parse service locator." 
                                               delegate:nil 
                                      cancelButtonTitle:@"OK" 
                                      otherButtonTitles:nil] show];
                });
            }
            
        } else {
            NSLog(@"Unable to load service locator because of error: %@", error);
            
            // inform the user on the UI thread
            dispatch_async(dispatch_get_main_queue(), ^{
                [[[UIAlertView alloc] initWithTitle:@"Error" 
                                            message:@"Unable to load service locator.  Did you remember to update the URL to your own copy of it?" 
                                           delegate:nil 
                                  cancelButtonTitle:@"OK" 
                                  otherButtonTitles:nil] show];
            });
        }
    });
    

    }

    这个json运行于服务器上,应用就能请求到serviceLocator并解析.

    HTTP协议

    一个URL只对应一个资源,多个URL可对应同一个资源.(localhost是个例外)
    URL由5部分构成:

    http://user:password@hostname:port/absolute-path?query

    协议 认证 主机名 端口 绝对路径 查询字符串

    协议

    除了HTTP外还可以使用FTP协议。iOS应用中常用的另外一种协议还有FILE ,用于请求在应用sandbox中的本地资源,如果使用字符串而没有使用协议创建NSURL对象,那么默认就会使用FILE协议。

    端口

    HTTP默认端口是80,HTTPS默认端口443(有些网络代理和防火墙会处于安全或者隐私考虑等原因阻塞非标准端口)

    绝对路径

    很多REST服务使用路径来传递值,用来唯一标识数据库中存储的实体。比如/customer/456/address/0指定索引为0的地址,具有标识456的用户.

    查询字符串

    多个查询参数用&字符分隔,查询字符串不可以包含回车、空格与换行符。
    iOS为NSString对象提供了一个方法来执行URL的百分号编码

        NSString *urlString = @"http://myhost.com?query=This is a question";
    NSString *encoded = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    

    结果为http://myhost.com?query=This%20is%20a%20question.该编码不同于URL编码,不会对&字符编码,因此也不会改变URL参数的分隔.URL编码会编码& ? 和其他标点符号,如果查询的字符串包含了这些字符,那么需要实现一种更为彻底地编码方法。

    请求内容

    HTTP请求包含三部分:请求行、请求头与请求体。请求行和请求头是文本行,通过回车/换行符分隔(值为13的字节,或是0x0D/值为10的字节,或是0x0A).在HTTP请求中这样使用文本值,使得它们很容易构建、解析和调试。空行(仅包含回车/换行符或是仅有换行符)将请求头与请求体分隔开。

    HTTP请求实例:

    GET /search?source=ig&h1=en&rlz=&q=ios&btnG=Google+Search HTTP/1.1
    HOST: www.google.com
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0)...
    Accept: text/html,application/xhtml+xml,application/xml:q=0.9,*/*;q=0.8
    Accept-Language: en,en-us;q=0.7,en-ca;q=0.3
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Referer: http://www.google.com/ig?hl=en&source=webhp
    Cookie: PREF=ID=fdf9979...
    

    请求行是发送给服务器的第一行数据。请求行包括3方面主要信息:HTTP请求方法、请求URI与HTTP版本.

    标准请求方法都是大写。

    GET

    从服务器获取一段内容(用HTTP术语来说,就是实体entity),不会导致服务器端的数据发生变化,GET请求通常不包括请求体,不过也可以包含。

    POST

    使用客户端提供的数据更新实体.POST请求通常会在请求体中加入应用服务器(运行php等?)所需的信息.POST请求是非幂等(待查)的.这意味着如果处理多个请求,那么结果与处理单个请求是不同的。

    HEAD

    获取响应的元数据而无需检索响应的全部内容。该方法通常用于检查服务器最近的内容变化而无须检索全部内容

    PUT

    使用客户端提供的数据添加实体。PUT请求通常将应用服务器所需的信息放在请求体中来创建新的实体。在通常情况下,PUT请求是幂等的,这意味着多个请求的处理会产生相同的结果。

    DELETE

    根据URI的内容或客户端提供的请求体来删除实体。DELETE请求是REST服务接口中使用最为频繁的请求。

    说明:
    HTTP规范允许HTTP客户端与服务器之间的中介添加、删除、重排序以及修改HTTP头。因此,应用发出的请求在到达服务器时可能会出现以下的情况:添加新的头,修改已有的头或者删除某些头。

    虽然使用了有状态的TCP传输层,但HTTP却是个无状态协议。这意味着HTTP服务器并不会保留关于某个请求的任何信息以用在未来的请求中。Cookie提供了一种方式,可以将一些简单地状态信息存储在客户端,并在后续的请求中与服务器进行通信。
    HTTP头之后是可选的请求体。请求体可以使任意的字节序列,通过一个空行与头分割开来,请求体必须遵循客户端与服务器之间预先确定的数据编码。对于Web浏览器来说,这通常是表单编码的数据,但对于移动应用来说,通常是JSON数据。在iOS中NSURLRequest及其子类NSMutableURLRequest提供了必要的方法与属性来构建几乎所有的HTTP请求。
    在HTTP服务器与应用服务器处理完请求后,HTTP响应会通过同一个TCP socket[待查]返回给客户端.HTTP响应的结构类似于HTTP请求,第一行也是状态行,后面是头,然后是响应体。
    简单的HTTP响应示例:

    HTTP/1.1 200 OK
    Date: Tue, 27 Mar 2012 12:25;12 GMT
    Expires: -1
    Cache-Control: private, max-age=0
    Content-Type: text/html; charset=UTF-8
    Content-Encoding: gzip
    Transfer-Encoding: chunked
    Server: gws
    <!doctype html><html itemscope="itemscope"
    itemtype="http://schema.org/Webpage">
    <head><meta itemprop="image" content="/images/google_favicon_128.png"/>
    <title>ios - Google Search</title>
    <script>window.google=(kEI:"prlxT5qtNqe70AHh873aAQ",
    getEI:function(a)(var b;
    while(a&&!(a.getAttribute&&(b=a.getAttribute("eid"
    

    状态行包括3个域,域之间通过空格分隔。第一个域是响应HTTP的版本。接下来的两个域提供了表示请求结果的状态值。首先是一个3位的整数值,包含了请求的结果代码,最后是条说明语句,提供了关于代码的简短文本说明。在大多数情况下,数值能够完全说明状态。
    紧跟状态行的是响应头:响应头之间通过回车/换行符进行分隔。每个头都包含了关于响应的元数据,包括数据的上一次修改时间、客户端可以缓存数据多少时间、数据的编码方式以及在随后的请求中提交的状态信息。
    响应体是通过空行与响应头分隔开的。响应体可以包含任意数量的二进制字符。与客户端通信的响应体的长度可以通过请求的Content-Length头或者块编码体现。块编码响应包含Transfer-Encoding头,并且带有chunked值。块编码体包含一个或(多个)(体片段).每个片段都有起始行,指定块中字节数量,iOS URL加载系统向应用隐藏了这种复杂性。
    在iOS的URL加载系统中,NSURLResponse及其子类NSHTTPURLResponse封装了请求返回的数据。该继承体系中有两个对象,因为URL加载可以基于非HTTP URL实现数据的请求。比如,对URL file://的请求就不会包含任何头信息.

    高层iOS HTTP API

    主要有3个方法可以执行HTTP请求和接受响应:

    • 同步。启动线程 的 代码会阻塞,直到整个响应加载完毕并返回到调用方法为止。该技术最容易实现,不过局限性也最大。
    • 队列式异步。起始代码创建一个请求,并将其放到一个队列中以在后台线程中执行。
    • 异步。起始代码 开启一个请求,该请求运行在起始线程中,不过在请求处理时会调用委托方法,该方法的实现最为复杂,不过在处理响应时却提供了最大的灵活性。(在特定时刻执行回调函数,方便进行操作)
      所有这三个请求都由相同的4个对象构成:NSURL,NSURLRequese,NSURLConnection,NSURLResponse
    NSURL

    可以通过NSURL对象轻松管理URL值并访问URL所指向的内容。NSURL可以指向文件资源,也可以指向网络资源,同时在这两类资源类型的使用上没有任何区别。从URL加载数据示例代码:

    NSURL *url = [NSURL urlWithString:mysteryString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    

    mysteryString的值可以引用文件或网络资源,而代码的行为是一样的。主要的差别在于加载mysteryString所引用的资源的时间上。如果URL引用的是网络资源,就会在后台现成执行代码,这样在数据加载时用户界面就不会暂停下来。(也就是自动转到后台操作咯?不错)
    创建NSURL对象最常见的方式是使用类方法 URLWithString:进行实例化。该方法会创建一个NSURL对象,并使用提供的NSString对象的内容对其进行初始化,下面这段代码说明了这一点:

    NSURL *url = [NSURL URLWithString:@"http://www.wiley.com/path1"];
    

    NSURL对象提供了很多访问器方法来读取URL各个部分的值。每个访问器都可以只读访问URL的一部分。scheme访问器会返回一个包含该URL所用协议的NSString对象。如果目标URL没有指定某个特定部分,那么返回值就为nil,考虑之前创建的url对象,下面代码会打印出 Port is nil.

    if (url.port == nil){
        NSLog(@"Port is nil");
    } else {
        NSLog(@"Port is not nil");
    }
    

    如果URL包含查询字符串,那么query访问器方法就会包含所有需要查询参数的值。根据RFC 3986的要求,在创建NSURL对象前,URL字符串的内容需要以百分号编码。比如,如果执行下述代码片段,那么查询参数的值就为q=iOS+Networking

    NSURL *url = [NSURL URLWithString:@"http://google.com?q=iOS+Networking"];
    

    NSURL对象是不可变的,这意味着无法先构建空的NSURL对象,然后通过调用对象的赋值方法(有时也叫setter)方法来装配其属性。对象要么1.通过NSString对象,要么2.通过另一个NSURL对象实例化.如果用于实例化NSURL对象的字符串是不合法的,那么创建方法就会返回nil。在使用URL对象进行网络请求前,应该验证URL对象是合法的。

    NSURLRequest

    创建好NSURL对象后,接下来就需要完成执行HTTP请求所需的下一步了:创建NSURLRequest对象。NSURLRequest对象包含了加载URL内容所需的信息,并且独立于URL中指定的协议。iOS中的URL加载系统支持HTTP,HTTPS,FTP,FILE URL内容的加载。URL加载系统提供了一种扩展方式以处理新的协议,方式是创建NSURLProtocol自雷,然后将返回结果提供给URL加载系统。
    创建NSURLRequest对象最简单的方式是通过类方法和提供的NSURL对象。下述代码片段展现了使用默认值来创建请求对象的过程

    NSURL *url = [NSURL URLWithString:@"https://gdata.youtube.com/feeds/api/standardfeeds/top_rated"];
    if (url == nil) {
        NSLog(@"Invalid URL");
        return;
    }
    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    if (request == nil) {
        NSLog(@"Invalid request");
        return;
    }
    

    使用默认值表示请求使用URL协议指定的请求缓存规则,请求有着标准的请求超时。如果URL是HTTP或HTTPS,那么请求方法将是GET。并且使用操作系统提供的默认头。
    下列示例展示了如何使用自定义的缓存和超时值来创建NSURLRequest对象。构建URL加载系统的代码忽略了所有缓存,如果完成请求连接的时间超过20s,将会发生错误.
    NSURLRequest对象提供了几个访问器方法来获取请求的属性,由于NSURLRequest类是不可变的,因此无法更改这些属性(readonly)。除了URL、缓存策略和超时值之外,如果要修改其他属性,那么请用NSMutableURLRequest类。
    NSMutableURLRequest是NSURLRequest的子类,提供了赋值方法以修改请求的属性。下述代码片段展示了使用一小段消息体来创建一个简单的POST请求,它包含了以UTF8编码的一个NSString对象的字节。URL加载系统会自动装配请求的Content-Length头:

        NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
    [req setHTTPMethod:@"POST"];
    [req setHTTPBody:[@"Post body" dataUsingEncoding:NSUTF8StringEncoding]];
    

    有两种方式可以向NSURLRequest提供HTTP体:在内存中(就像之前的示例一样)或是通过NSInputStream。代码可以通过输入流提供请求体而无需将整个体加载到内存中。如果发送诸如照片或视频等大容量内容,那么使用输入流是最佳选择。下述代码片段展示了如何通过输入流创建POST方法,需要事先将NSString srcFilePath设定为应用包或是沙盒中的文件路径

        NSArray *srcArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *srcFilePath = srcArray[0];
    srcFilePath = [srcFilePath stringByAppendingString:@"/filePath"];
    NSInputStream *inStream = [NSInputStream inputStreamWithFileAtPath:srcFilePath];
    [req setHTTPBodyStream:inStream];
    [req setHTTPMethod:@"POST"];
    

    由于NSURLRequest对象包含HTTP与非HTTP请求的属性,因此访问非 HTTP URL的代码需要将特定于HTTP的属性的值设为nil.

    NSURLConnection

    NSURLConnection对象是URL加载系统活动的中心,但提供的接口却不多,只提供了用于初始化、开启与取消连接的方法。。
    回到上面提到的用于执行HTTP请求和获取响应的主要方法上来,NSURLConnection类通过3种不同的操作模式发挥作用:同步,异步,队列式异步。同步模式是最易于使用的,不过却有很多限制,这使得它不太适合更加高级的交互。异步模式提供了很大的灵活性,不过其代价就是增加了代码的复杂性。队列式异步模式提供了异步模式的后台操作,同时又保持了同步模式的简单性。
    在异步模式下操作时,NSURLConnection对象会调用委托对象来指引连接流、处理到来的数据、处理认证并对错误做出响应。

    NSURLResponse

    NSURLReponse对象会再URL加载请求完毕后返回。响应对象的内容根据请求的类型及成功与否会有较大变化。如下列表介绍了从请求返回的各种对象。还有两个对象也可能来自于URL加载请求:NSError对象和NSData对象。如果请求有问题或者客户端无法连接到服务器,就会产生NSError对象。如果有响应返回,那么生成的NSData对象就会包含响应体。如果生成了NSError对象,就不会再生成NSURLResponse与NSData 对象。

    • MIMEType--结果数据的MIME类型。该值来自于服务器,如果客户端框架认为服务器有错,那么可以修改,如果服务器没有提供,还可以由客户端框架提供。

    • expectedContentLength--该值可能由请求返回,也可能不返回,返回值可能与返回内容的实际大小不同。如果返回内容的大小未知,那么该值将等于NSURLResponseUnknownLength

    • suggestedFilename--要么是服务器提供的内容文件名,要么来自于URL和MIME
      URL--返回内容的URL。由于重定向和标准化等原因,该URL可能与提供的URL不同。

    • textEncodingName--最初的数据源所用的文本编码名。如果响应中没有使用文本编码,那么该值将会是nil.
      URL加载系统提供了一个名为NSHTTPURLResponse的NSURLResponse子类,它包含特定于HTTP请求的属性。该类对于确定HTTP请求的结果是必需的。它有如下参数:

    • 响应头--该属性返回响应头值得NSDictionary对象。字典的键是头的名字,每个键的值是头的值。HTTP规范孕育一个请求有多个同名的头。NSHTTPURLResponse通过返回一个包含所有头值的NSString对象(头值之间用逗号分隔)来处理这一点。

    • HTTP状态码--来自于响应状态行的整数状态码。NSHTTPURLResponse类有一个类方法可以针对任意状态返回本地化的字符串说明。

    同步请求

    同步请求是iOS中最简单的请求类型,不过简单的代价则是缩减的功能与灵活性的降低。在发出同步请求时,请求所处的线程就会阻塞,直到请求完成或失败为止。同步请求通常用于创建HTTP GET请求后在后台线程中获取已知大小的资源。比如使用同步请求在后台线程中可以轻松获取图片并显示在单元格中。
    同步请求展示了获取URL数据的最简单方式,在iOS API中有很多辅助方法的底层使用的都是同步请求。比如,NSString stringWithContentsOfURL:方法会创建一个NSString实例,然后根据URL的内容从任意服务器获取这些内容。如果URL使用了FILE(如:file://foo.txt)协议,就会从本地文件系统获取内容。如果URL使用了HTTP(如:http://www.wiley.com)协议,就会从远程服务器获取内容。因此,除非知道URL是FILE URL,否则在使用这些辅助方法从URL中获取内容时务必小心。

    - (NSArray *) doSyncRequest:(NSString *)urlString {
    // make the NSURL object from the string
    NSURL *url = [NSURL URLWithString:urlString];
    
    // Create the request object with a 30 second timeout and a cache policy to always retrieve the
    // feed regardless of cachability.
    NSURLRequest *request = 
       [NSURLRequest requestWithURL:url 
                        cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData 
                    timeoutInterval:30.0];
    
    // Send the request and wait for a response
    NSHTTPURLResponse   *response;
    NSError             *error;
    NSData *data = [NSURLConnection sendSynchronousRequest:request 
                                         returningResponse:&response 
                                                     error:&error];
    // check for an error
    if (error != nil) {
        NSLog(@"Error on load = %@", [error localizedDescription]);
        return nil;
    }
    
    // check the HTTP status
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        if (httpResponse.statusCode != 200) {
            return nil;
        }
        NSLog(@"Headers: %@", [httpResponse allHeaderFields]);
    }
    
    // Parse the data returned into an NSDictionary
    NSDictionary *dictionary = 
        [XMLReader dictionaryForXMLData:data 
                                  error:&error];
    // Dump the dictionary to the log file
    NSLog(@"feed = %@", dictionary);
    
    NSArray *entries =[self getEntriesArray:dictionary];
    
    // return the list if items from the feed.
    return entries;
    }
    

    代码清单3-1从包含URL的调用者中接受一个NSString对象.当URL对象构建完毕后,会实例化一个NSURLRequest对象。在该例中,代码重写了默认的缓存策略和超时时间。注意:这里通过NSURLRequestReloadIgnoringLocalAndRemoteCacheData将缓存策略设为用不缓存。这样可以更好地表现出同步请求对UI的影响,因为UI线程会被阻塞。通常情况下,代码不会重写所有缓存,不过重写默认的超时时间倒是比较常见的,这里为请求指定30s的超时时间。
    创建好请求后,代码会调用NSURLConnection的类方法sendSynchronousRequest:returningReponse:error:来执行请求。该方法将请求和两个指针作为传入参数:一个指向NSURLResponse对象,它会由服务器的响应进行装配;另一个指向NSError对象,如果请求失败,该对象中就包含详细的错误信息。response指针指向NSURLResponse的一个实例,然而,它将是用于处理所有HTTP请求的NSHTTPURLResponse子类实例。如果NSError不为nil,那就说明请求在某个底层失败了,然而,如果为nil,那就表明请求没有因为网络错误或是不合法的URL而失败。但请求仍旧可能在语义上失败,比如服务器响应说遇到了内部服务器错误等。error指针指向的NSError对象的内容包含了关于错误的详细说明信息。
    代码清单3-1中的代码会检查NSError对象是否存在,以及NSHTTPURLResponse对象的状态码。如果两者都标识为成功,那么方法就会继续执行。
    sendSynchronousRequest:returingResponse:error方法会以NSData对象的形式返回HTTP相应体。由于源以XML形式表示,因此请求成功的NSData对象会被XMLReader读取器解析到NSDictionary中,接下来会遍历该字典,将RSS条目列表返回给调用者。
    创建同步调用相当简单,仅需几行代码就可以成功从服务器获取数据,不过这种简单性是以有限的使用场景和增加缺陷的风险为代价的。
    同步请求的best practice:

    • 只在后台线程中使用同步请求,除非确定请求访问的是本地文件资源,否则请不要在main_queue中使用!
    • 只有在知道返回数据不会超出应用的内存时才可以使用同步请求。记住,整个响应体都会位于代码的内存中。如果响应很大,那么可能导致应用出现内存溢出(memory leak)的问题。此外,当代码将响应解析为所需的格式时可能需要复制(类似copy语义?)返回的数据,这会导致内存增加一倍。
    • 在处理返回的数据前,验证错误与调用返回的HTTP响应状态码(一般是200?)
    • 如果源URL需要验证,那么不要使用同步请求。因为同步框架并不支持对认证请求作出响应。唯一的例外是BASIC认证,因为这时认证信息可以通过URL或请求头进行传递。以这种方式执行认证会增加应用与服务器之间的耦合度,从而导致整个应用变得脆弱。如果请求不使用HTTPS协议,那么还是会再明文中传递认证信息。
    • 如果需要向用户提供进度条,那么不要使用同步请求,因为请求是原子的(个人理解:加锁后,一次只能由一个东西对它访问,因而不能显示进度条),无法提供中间的进度提示信息。
    • 如果需要流解析器[待查]来渐进解析响应数据,那么不要使用同步请求
    • 如果在请求完成前需要取消,那么不要使用同步请求。

    队列式异步请求

    队列式异步请求类似于同步请求。程序提供NSURLRequest对象,URL加载系统尝试加载请求而不会与调用代码之间存在任何其他的交互。这两种方式之间的主要差别在于URL加载系统执行的队列式异步请求位于队列中,可能位于后台线程上。队列式异步请求的概念是在iOS 5.0中增加的。
    iOS提供了一种叫做操作队列的设施(NSOperationQueue)。这些队列可以让程序描述待执行的操作,然后以 FIFO的顺序提交操作供队列执行。队列框架提供了优先级顺序以及根据操作依赖的顺序,不过URL加载系统并没有使用这些设施。
    在代码构建队列式异步请求前,首先需要创建队列,里面是执行的请求。下述代码展示了如何创建操作队列:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    

    操作队列可以并发执行多个操作。在默认情况下,并发操作的数量是由iOS根据系统情况决定的。可以通过调用创建队列的setMaxConcurrentOperationCount:方法来重写默认值。当应用启动时,一个队列就会自动创建,可以通过调用NSOperationQueue的mainQueue类方法来获取该队列。不要使用该队列做网络请求,因为会在主线程上操作,长时间操作会冻结用户界面。如果从测试应用的分隔控件中选择队列选项并tap刷新按钮,那么会发现刷新按钮会立刻返回默认状态。同时表现为tableView会被清空。之所以会出现这种情况是因为请求是排队的,主运行循环会继续执行而不会等待请求完成。

    - (void) doQueuedRequest:(NSString *)urlString  delegate:(id)delegate {
    // make the NSURL object
    NSURL *url = [NSURL URLWithString:urlString];
    
    // create the request object with a no cache policy and a 30 second timeout.
    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30.0];
    
    // If the queue doesn't exist, create one.
    if (queue == nil) {
        queue = [[NSOperationQueue alloc] init];
    }
    
    // send the request and specify the code to execute when the request completes or fails.
    [NSURLConnection sendAsynchronousRequest:request 
                                       queue:queue 
                           completionHandler:^(NSURLResponse *response, 
                                               NSData *data, 
                                               NSError *error) {
                               
            if (error != nil) {
               NSLog(@"Error on load = %@", [error localizedDescription]);
            } else {
                
                // check the HTTP status
                if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                    if (httpResponse.statusCode != 200) {
                        return;
                    }
                    NSLog(@"Headers: %@", [httpResponse allHeaderFields]);
                }
                               
                // parse the results and make a dictionary
                NSDictionary *dictionary = 
                   [XMLReader dictionaryForXMLData:data 
                                             error:&error];
                NSLog(@"feed = %@", dictionary);
    
                // get the dictionary entries.
                NSArray *entries =[self getEntriesArray:dictionary];
    
                // call the delegate
                if ([delegate respondsToSelector:@selector(setVideos:)]) {
                    [delegate performSelectorOnMainThread:@selector(setVideos:) 
                                               withObject:entries 
                                            waitUntilDone:YES];
                }
            }
    }];
    }
    

    代码清单3-2展示的方法用于创建和处理排队请求的结果。注意这种另类的delegate方式(不知道写法优劣如何)。与同步请求一样,首先会创建一个NSURL对象,然后向其传递一个新的NSURLRequest对象。请求创建完毕之后,如果queue不存在,那么代码会创建一个名为queue的NSOperationQueue对象。该变量在FeedLoader类的实现中被声明为静态变量。通常情况下,应用会在启动时在应用委托中创建一个队列,然后在整个应用中都使用该队列。由于知道队列已经存在,因此代码会调用NSURLConnection来执行队列中的请求,并在操作完成或失败后调用一个块(就是那个 completionHandler)。当请求位于队列中时,doQueueRequest:delegate方法会返回到调用者。由于方法会在URL加载完毕前返回(异步),因此当加载完毕时需要由一个委托类去调用(id类型的delegate)。由于这里使用了异步完成模式,因此代码需要实现委托或通知模式,从而将接受到的数据传递给最初的请求对象。
    待执行的代码块通过sendAsynchronousRequest:queue:completionHandler方法的completionHandler参数传递。completion块验证请求没有生成错误,并且HTTP状态码为200.这表示成功。如果请求是成功的,那么返回的数据就会被解析到NSDictionary中。接下来,代码会验证所提供的委托类支持setVideos:方法(respondsToSelector:)。如果支持,就会在主线程上调用该方法,并提供RSS源返回的条目数组。setVideos:之所以要在主线程上调用,是因为completion块是在后台线程在执行的。如果委托方法在该context中执行并操作UI界面,那么结果是undefined,大多数情况下都是不正确的。
    队列式异步请求的best practice:

    • 只有在知道返回的数据不会超过应用的内存时才能使用队列式异步请求操作。记住,整个响应体都会位于代码的内存中。如果响应很大,那么可能导致应用出现memory leak问题。此外,当代码将响应解析为所需格式时可能需要复制返回的数据,这会导致内存增加一倍。
    • 为所有操作使用单一的NSOprationQueue,根据服务器的能力以及预期的网络状况控制当前操作的最大数量。
    • 在处理返回的数据前验证错误与调用返回的HTTP响应状态码
    • 如果源URL需要验证,那么不要使用队列式异步请求,因为该功能并不支持对认证请求作出响应。如果服务需要验证,那么可以将BASIC认证信息放在提供给请求的URL中(感觉相当不安全啊)
    • 如果需要向用户提供进度条,那么不要使用队列式异步请求,因为请求是原子的,无法提供中间的进度指示信息。
    • 如果需要流解析器来渐进解析响应数据,那么不要使用队列式异步请求。
    • 如果请求在完成前需要取消,那么不要使用队列式异步请求。(跟同步请求有相当一部分的类型要求)

    异步请求

    异步请求使用与同步和队列式异步请求相同的对象,只不过又增加了另一个对象,即NSURLConnectionDelegate对象
    协议处理器在HTTP协议过程中处理时,会在连接的重要阶段调用委托方法.
    协议处理器在调用方法前会先验证委托实现了该方法。如果没有实现,那么协议处理器就会假定一个默认值并在连接中继续处理。代码清单3-3包含了使用异步技术初始化URL加载请求的代码。一开始,该方法与之前的技术很类似:创建NSURL对象,然后用来构建请求。当请求构建网壁厚,代码会创建NSURLConnection对象并将自身作为委托对象。在URL内容加载时,协议处理器会调用委托类并提供关于请求状态的信息。借助于这些回调,委托类可以调整协议处理器的行为。
    创建好连接后,代码会开始请求。在连接创建与开启连接之间,应用可以修改委托消息传递给委托类的方式。代码可以指定不同的运行循环或操作队列来传递回调。

    /**
     * Creates a UUID to use as the temporary file name during the download
     */
    - (NSString *)createUUID
    {
        CFUUIDRef uuidRef = CFUUIDCreate(NULL);
        CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
        CFRelease(uuidRef);
        NSString *uuid = [NSString stringWithString:(__bridge NSString *)
                          uuidStringRef];
        CFRelease(uuidStringRef);
        return uuid;
    }
    

    3-3
    - (void) start {
    NSLog(@"Starting to download %@", srcURL);

    // create the URL
    NSURL *url = [NSURL URLWithString:srcURL];
    
    
    // Create the request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    // create the connection with the target request and this class as the delegate
    self.conn = 
         [NSURLConnection connectionWithRequest:request 
                                       delegate:self];
    
    // start the connection
    [self.conn start];
    }
    

    示例应用实现了几个委托方法供调用,同时又有几个方法没有实现。委托方法是由两个协议定义的:NSURLConnectionDelegate与NSURLConnectionDataDelegate.接下来会回顾已经实现的方法和未实现的方法.

    - (NSURLRequest *)connection:willSendRequest:redirectResponse:

    #pragma mark NSURLConnectionDelegate methods
    /**
     * This delegate method is called when the NSURLConnection gets a 300 series response that indicates
     * that the request needs to be redirected.  It is implemented here to display any redirects that might
     * occur. This method is optional.  If omitted the client will follow all redirects.
     **/
    - (NSURLRequest *)connection:(NSURLConnection *)connection 
                 willSendRequest:(NSURLRequest *)request 
                redirectResponse:(NSURLResponse *)redirectResponse {
        
        // Dump debugging information
        NSLog(@"Redirect request for %@ redirecting to %@", srcURL, request.URL);
        NSLog(@"All headers = %@", 
              [(NSHTTPURLResponse *)redirectResponse allHeaderFields]);
        
        // Follow the redirect
        return request;
    }
    

    如果协议处理器接收到来自服务器的重定向请求,就会调用该方法。HTTP重定向指的是这样一种HTTP响应,它们通知客户端要寻找的内容位于另一个不同的URL中。如果应用从内容分发网络加载内容,那么重定向请求就是很常见的.该委托方法总是在其他传递响应或体数据的方法前得到调用。由于请求可以执行多个重定向,因此对于某个请求来说,该方法可能不会被调用,也可能被调用多次。如果委托没有实现该方法,那么协议处理器就会将重定向转向新的位置。通过实现该方法,代码可以拦截重定向,并且根据重定向的特点终止连接或修改请求。在该例中,代码会执行调试功能,将重定向请求头信息的日志打印出来,然后再执行重定向。

    (void)connection:didReceiveResponse:

    如代码清单3-5.在协议处理器从响应头构建出响应对象后,会调用该方法。

        /**
         * This delegate method is called when the NSURLConnection  connects to the server.  It contains the 
         * NSURLResponse object with the headers returned by the    server.  This method may be called multiple times.
         * Therefore, it is important to reset the data on each call.  Do not assume that it is the first call
     * of this method.
     **/
    - (void) connection:(NSURLConnection *)connection 
     didReceiveResponse:(NSURLResponse *)response {
        NSLog(@"Received response from request to url %@", srcURL);
        
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSLog(@"All headers = %@", [httpResponse allHeaderFields]);
        
        if (httpResponse.statusCode != 200) {// something went wrong, abort the whole thing
            // reset the download counts
            if (downloadSize != 0L) {
                [progressView addAmountToDownload:-downloadSize];
                [progressView addAmountDownloaded:-totalDownloaded];
            }
        [connection cancel];
        return;
    }
    
    
    NSFileManager *fm = [NSFileManager defaultManager];
    
    // If we have a temp file already, close it and delete it
    if (self.tempFile != nil) {
        [self.outputHandle closeFile];
        
        NSError *error;
        [fm removeItemAtPath:self.tempFile error:&error];
    }
    
    // remove any pre-existing target file
    NSError *error;
    [fm removeItemAtPath:targetFile error:&error];
    
    // get the temporary directory name and make a temp file name
    NSString *tempDir = NSTemporaryDirectory();
    self.tempFile = [tempDir stringByAppendingPathComponent:[self createUUID]];
    NSLog(@"Writing content to %@", self.tempFile);
    
    // create and open the temporary file
    [fm createFileAtPath:self.tempFile contents:nil attributes:nil];
    self.outputHandle = [NSFileHandle fileHandleForWritingAtPath:self.tempFile];
    
    // prime the download progress view
    NSString *contentLengthString = [[httpResponse allHeaderFields] objectForKey:@"Content-length"];
    // reset the download counts
    if (downloadSize != 0L) {
        [progressView addAmountToDownload:-downloadSize];
        [progressView addAmountDownloaded:-totalDownloaded];
    }
    downloadSize = [contentLengthString longLongValue];
    totalDownloaded = 0L;
    
    [progressView addAmountToDownload:downloadSize];
    }
    

    当协议处理器接收到足够的数据来创建URL响应对象时会调用didReceiveResponse方法。如果在接收到足够的数据来构建响应对象前出现了错误,就不会调用该方法。在示例代码中,委托方法会验证响应对象的HTTP状态。如果状态不是200,那么请求的加载就会被取消,提供下载进度的视图会被重置。如果状态是200,那么代码会更新进度视图,方式是讲所需的数据量加起来,然后创建临时文件来接收HTTP响应体,临时文件稍后会被传给另一个委托方法。
    该方法可能会被协议处理器调用多次;因此,代码必须重新开始这一场景。在实例中,重新开始 逻辑 包括重置进度显示器。如果之前响应的临时文件存在,那么还需要将其删除。

    connection:didReceiveData:

    如代码清单3-6所示。当协议处理器接收到部分或全部响应体时会调用该方法。该方法可能不会被调用,也可能会调用多次,并且调用总是跟在最初的connection:didReceiveResponse:之后。如果需要渐进地解析响应,那么流处理器应该充分利用该方法。

        /**
     * This delegate method is called for each chunk of data received from the server.  The chunk size
     * is dependent on the network type and the server configuration.  
     */
    - (void)connection:(NSURLConnection *)connection 
        didReceiveData:(NSData *)data {
        // figure out how many bytes in this chunk
        totalDownloaded+=[data length];
        
        // Uncomment if you want a packet by packet log of the bytes received.  
        NSLog(@"Received %lld of %lld (%f%%) bytes of data for URL %@", 
              totalDownloaded, 
              downloadSize, 
              ((double)totalDownloaded/(double)downloadSize)*100.0,
              srcURL);
        
        // inform the progress view that data is downloaded
        [progressView addAmountDownloaded:[data length]];
        
        // save the bytes received
        [self.outputHandle writeData:data];
    }
    

    该方法可能不会被调用,也可能调用多次,这取决于响应体的大小。在每次调用时,协议处理器就会在data参数中传递部分体数据。该委托方法负责聚集所提供的数据对象,然后处理他们或是将其存储起来。所提供的数据块大小可能与应用协议的语法边界不一致,换句话说,如果代码接收的是XML文档,那么数据对象可能与文档中的元素边界不一致,在示例中,应用首先将connection:didReceiveResponse:方法中接受到得字节追加到数据文件中。

    connection:didFailWithError:

    如代码清单3-7所示,当连接失败时会调用这个委托方法,该方法可能会在请求处理的任何阶段得到调用。

    /**
     * This delegate methodis called if the connection cannot be established to the server.  
     * The error object will have a description of the error
     **/
    - (void)connection:(NSURLConnection *)connection 
      didFailWithError:(NSError *)error {
        NSLog(@"Load failed with error %@", 
              [error localizedDescription]);
        
        NSFileManager *fm = [NSFileManager defaultManager];
        
        // If we have a temp file already, close it and delete it
        if (self.tempFile != nil) {
            [self.outputHandle closeFile];
            
            NSError *error;
            [fm removeItemAtPath:self.tempFile error:&error];
        }
        
        // reset the progress view
        if (downloadSize != 0L) {
            [progressView addAmountToDownload:-downloadSize];
            [progressView addAmountDownloaded:-totalDownloaded];
        }
    }
    

    如果被调用,那么该方法将是该连接调用的最后一个方法。示例应用只是在连接失败时打印出错误信息。如果临时下载文件存在,那么将会关闭临时文件,并调整进度指示器以反映出中断的下载。一旦该方法返回,协议处理器将取消请求。这个方法很适合分析器采用,这样就可以对应用调用的端点失败率作出定量度量。

    connectionDidFinishLoading委托方法

    如代码清单3-8所示,该例实现的最后一个委托方法是 connectionDidFinishLoading.当整个请求完成加载并且接收到的所有数据都被传递给委托后,就会调用该委托方法。

    /**
     * This delegate method is called when the data load is complete.  The delegate will be released 
     * following this call
     **/
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        // close the file
        [self.outputHandle closeFile];
    
    // Move the file to the target location
    NSFileManager *fm = [NSFileManager defaultManager];
    NSError *error;
    [fm moveItemAtPath:self.tempFile 
                toPath:self.targetFile 
                 error:&error];
    
    // Notify any concerned classes that the download is complete
    [[NSNotificationCenter defaultCenter] 
     postNotificationName:kDownloadComplete 
     object:nil 
     userInfo:nil];
    }
    

    该方法是为连接调用的最后一个方法,并且此调用与connection:didFailWithError:的调用是互斥的。示例应用会讲聚集所有接收到数据的文件关闭掉,根据最初请求的URL将文件移到某个位置,然后通过NSNotificationCenter通知视图控制器下载完毕。
    连接委托还可以实现其他几个方法来增加可以信息并实现对连接的控制。下面将介绍这些方法

    connection:needNewBodyStream:

    该方法是可选的,只是用于向请求体的输入流发出请求。如果协议处理器由于出现错误或是认证等原因需要重新传递请求体就会调用该方法。如下面的代码片段所示,该方法签名会接收NSURLConnection对象(用于请求新的数据流)以及触发该委托回调的request:

    - (NSInputStream *)connection: (NSURLConnection *)connection needNewBodyStream:(nonnull NSURLRequest *)request
    

    connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite

    如下代码片段所示,该可选的委托方法对象提供了上传进度信息:

    - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
    }
    

    协议处理器会在不确定的时间间隔内调用该委托方法以报告上传进度。bytesWritten与totalBytesWritten值可能不会一直增加。这是因为如果出现错误或是认证问题,就需要重新传递请求体。如果想向用户提供上传进度指示器,那么应该实现该方法。

    connection:willCacheResponse:

    如下代码片段展示,该可选方法向委托提供了一种方式来检测与修改协议控制器所缓存的响应:

    - (NSCachedURLResponse *)connnection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
    

    NSCachedURLResponse对象包含了NSURLResponse对象与请求返回来的以NSData形式存在的数据。该对象还包含了响应保持所需的存储策略,包括持久化储存、仅内存存储或不允许存储。缓存下来的响应对象还包含目录userInfo,可被应用用来存储缓存请求的元数据。如果该委托实现返回nil,那么响应就不会被存储下来。

    认证委托方法

    有5个委托方法与URL请求的客户端认证有关。

    异步请求与运行循环

    异步请求需要运行循环。当数据传递到服务器或是被客户端接收时,运行循环用于实现事件与委托对象之间的通信。异步请求在发出时,会在当前线程的运行循环上操作。这个实现细节是很重要的,因为在GCD块中或是通过NSOperationQueue创建的线程并没有运行循环。因此,如果在后台线程上发出了异步请求,那么还需要确定线程是由运行循环还是使用了别的运行循环。如下代码片段展示了如何显式地将请求处理指定给运行循环:

    NSURLRequest *request;
    NSURLConnection *connection1 = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [connection1 scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [connection1 start];
    

    第一个操作创建了NSURLConnection对象,不过并没有立刻启动方法,这样就可以进一步初始化了。下一行代码获取到主线程的运行循环(即runloop),然后将它提供给连接,作为其运行循环。最后,连接通过start方法开始处理。如果不想再主运行循环中执行异步请求,那么需要在另一个线程上创建runloop,然后针对这个新创建的runloop调度connection.

    异步请求的best practice:

    • 对于大的上传或下载来说,请使用异步请求以减少应用的内存占用量
    • 在需要认证的情况下请使用异步请求
    • 如果需要向用户提供进度反馈,那么请使用异步请求
    • 在后台线程上使用异步请求时要小心,请提供一个runloop
    • 对于可以在后台线程的请求队列中轻松调度和完成的简单请求来说,这时使用异步请求有些过犹不及
    • 如果使用输入流来上传数据,请实现connection:newBodyStream:方法以避免对输入流的复制

    高级HTTP操作

    HTTP头在提供可修改服务器响应的元数据以及向HTTP客户端提供额外信息方面扮演着重要角色。基于这一点,iOS开发者常常需要操纵请求头或是查看请求头。比如,有些服务器需要自定义的认证头来提供关于用户身份的信息。标准的URL加载系统并不会自动添加这些头,不过它提供了通过代码添加的方法。

    本节将会介绍更多地HTTP操作以及可以通过iOS的URL加载系统执行的操作。并且将会分别介绍如何创建与使用其他的HTTP请求方法、如何处理HTTP cookie以及关于HTTP的高级用法

    使用请求方法

    根据定义,GET请求不应该包含HTTP体,而只应该包含请求行和请求头。HTTP服务器会返回由URL指定的资源内容。网络设备常常会假定GEET请求完整的上下文位于请求行中,并根据这些数据来缓存响应。如果GET请求包含会修改请求所返回内容的请求体,那么由于中间网络设备的缓存行为,你可能会得到错误的结果。根据约定,GET请求不应导致服务器上的数据发生任何变化。

    HTML浏览器最早通过POST请求来发送或提交HTML表单,使用的是一种特定的数据编码,通过application/x-www-form-urlencoded这种Content-Type来指定。iOS应用通常都会使用POST请求向服务器发送XML或JSON数据。下述代码片段展示了如何创建JSON对象并将其作为请求体:

        NSError *error1;
    NSDictionary *dict = @{
                           @"animal" : @"dog",
                           @"name" : @"fido",
                           @"weight" : @"20"
                           };
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error1];
    NSURL *url;
    NSLog(@"Json = %@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    [request setHTTPBody:jsonData]; 
    

    上述代码首先创建了一个简单的NSDictionary对象,里面放置了一些虚构出来的动物的名值对。然后通过内建的JSON库,创建一个NSData对象来表示该字典。接下来将该NSData对象提供给NSMutableURLRequest作为请求体。该代码产生的JSON如下所示(第四章将会详细介绍构建和解析请求和响应负载的过程):

    {
        "weight" : "20",
        "animal" : "dog",
        "name" : "fido"
    }
    

    使用HEAD方法的请求会指示HTTP服务器只返回关于所请求的HTTP头信息。HEAD请求通常没有请求体,也没有响应体返回。它们常常用于验证缓存的数据与服务器上的数据,同时又不必获取缓存资源的整个内容。

    PUT请求类似于POST,因为它总是有请求体,但从语义上来说,两者有如下重要差别:PUT请求用于向服务器添加新的资源,而POST请求只用于更新服务器上的资源。在使用RESTful服务时,这种语义上的差别是非常重要的。

    操作cookie

    cookie是HTTP协议在首个版本之后加入的一个重要组件。它向服务器提供了追踪回话的能力,同时又无需维持客户端与服务器之间的连接。在浏览器客户端,cookie值是由服务器通过请求提供的,然后被放到随后的请求中。由于设计cookie的目的是追踪会话状态,因此它们通常会非常小,基本是几十到几百个字节。

    从服务器发送的cookie有几个属性用于确定cookie的值、何时返回到服务器以及客户端应该保存cookie的时间,这些属性有:

    • name--cookie的名字,从同一个DNS域返回的所有cookie名都是唯一的。只有name和value这两个属性才会在后续的请求中发送给服务器
    • value--由向服务器发送的下一个请求返回的值。
    • domain--后续请求在cookie中包含的DNS域。比如,拥有域值domain1.com的cookie不应该返回给domain2.com。如果忽略掉,那么客户端就会将URL的主机名当作域。如果域的最前面是个原点(.),那么cookie就会返回给发送到该域及其子域的任何请求。如果没有最前面的原点,那么cookie就只会包含在发送给该域而非其子域的请求中。
    • path--path限制发送给请求的cookie都是针对指定的URL路径。如果与DNS域搭配使用,那么path属性就可以限制只会将cookie发送给服务器上优先且精确的URL集合。
    • expiration date--cookie不再随请求发送的 日期和时间,cookie会在这个时间点从客户端存储中移除
    • session ONLY--指定cookie是在当前浏览器会话时间内返回还是一直持续到过期日期,以二者之间先到的时间为准,在iOS应用中,回话指的是OS加载应用到终止应用之间的应用生命周期
    • secure--指定cookie只会在HTTPS连接而非HTTP连接
    • comment--用于向用户说明cookie目的的注释值
    • comment URL-向用户提供了一个HTML文档,用于说明cookie的目的
    • HTTP ONLY--指示器,告诉客户端不要与javascript应用共享cookie以防止跨站脚本攻击
    • version--cookie遵循的HTTP cookie规范版本

    虽然不是浏览器,但iOS应用依然可以再HTTP连接中方便地使用cookie。URL加载框架帮我们做了大量繁杂的工作以利用协议的这个特性。下面是应用经常要使用到cookie的3个地方:

    • 检索cookie值
    • 显式删除cookie
    • 手工将cookie添加到请求中

    URL加载设施会自动处理所有HTTP与HTTPS请求中的cookie。会将返回的cookie保存在响应中,然后按照cookie处理规则将其添加到随后的请求中。只有在cookie的domain属性提供的DNS域

    URI、URL和URN之间的区别与联系:
    • URI:Uniform Resource Identifier,统一资源标识符;
    • URL:Uniform Resource Locator,统一资源定位符;
    • URN:Uniform Resource Name,统一资源名称。

    其中,URL,URN是URI的子集。

    HTTP的 URL示例:

    使用超级文本传输协议HTTP,提供超级文本信息服务的资源。
    例一:其计算机域名为www.peopledaily.com.cn。超级文本文件(文件类型为.html)是在目录/channel下的welcome.htm。这是中国人民日报的一台计算机。

    http://www.peopledaily.com.cn/channel/welcome.htm 
    

    例二:其计算机域名为www.rol.cn.net。超级文本文件(文件类型为.html)是在目录/talk下的talk1.htm。这是瑞得聊天室的地址,可由此进入瑞得聊天室的第1室

    http://www.rol.cn.net/talk/talk1.htm
    

    文件的URL示例:

    例一:代表存放主机ftp.yoyodyne.com上的pub/files/目录下的一个文件,文件名是foobar.txt。

    file://ftp.yoyodyne.com/pub/files/foobar.txt 
    

    例二:代表主机ftp.yoyodyne.com上的目录/pub。

    file://ftp.yoyodyne.com/pub 
    

    例三:代表主机ftp.yoyodyne.com上的根目录

    file://ftp.yoyodyne.com/ 
    
    文件的URL:
    用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存取路径(即目录)和文件名等信息。有时可以省略目录和文件名,但“/”符号不能省略。
    

    URI

    一般由三部分组成:

    • 访问资源的命名机制。
    • 存放资源的主机名。
    • 资源自身的名称,由路径表示。
    URL的格式

    由下列三部分组成:

    • 是协议(或称为服务方式);
    • 是存有该资源的主机IP地址(有时也包括端口号);
    • 是主机资源的具体地址。,如目录和文件名等。

    第一部分和第二部分之间用“://”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。


    相关文章

      网友评论

      本文标题:iOS网络编程读书笔记

      本文链接:https://www.haomeiwen.com/subject/syykkttx.html