返回介绍

iOS 中的 HTTP

发布于 2025-04-26 18:09:28 字数 11438 浏览 0 评论 0 收藏 0

React Native 包含了处理网络请求的模块:

#import "RCTDataManager.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@implementation RCTDataManager
RCT_EXPORT_MODULE()
/**
  * 处理网络请求


  * responseSender 块不会被相同的线程所调用


  */

RCT_EXPORT_METHOD(queryData:(NSString *)queryType
                  withQuery:(NSDictionary *)query
                  queryHash:(__unused NSString *)queryHash
                  responseSender:(RCTResponseSenderBlock)
                      responseSender)
{
  if ([queryType isEqualToString:@"http"]) {

    // 构建请求


    NSURL *URL = [RCTConvert NSURL:query[@"url"]];
    NSMutableURLRequest *request = [NSMutableURLRequest
        requestWithURL:URL];
    request.HTTPMethod = [RCTConvert NSString:query[@"method"]] ? :
        @"GET";
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"
    headers"]];

request.HTTPBody = [RCTConvert NSData:query[@"data"]];
// 构建

 dataTask
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
    dataTaskWithRequest:request
      completionHandler:^(NSData *data,
                            NSURLResponse *response,
                            NSError *connectionError) {
  // 构建响应


  NSDictionary *responseJSON;
  if (connectionError == nil) {
    NSStringEncoding encoding = NSUTF8StringEncoding;
    if (response.textEncodingName) {
      CFStringEncoding cfEncoding =
          CFStringConvertIANACharSetNameToEncoding((CFStringRef)
          response.textEncodingName);
      encoding = CFStringConvertEncodingToNSStringEncoding(
          cfEncoding);
    }
    NSHTTPURLResponse *httpResponse = nil;
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
      // 可能会是一个本地文件请求


      httpResponse = (NSHTTPURLResponse *)response;
    }
    responseJSON = @{
        @"status": @([httpResponse statusCode] ? : 200),
        @"responseHeaders": [httpResponse allHeaderFields] ? : @
            {},
        @"responseText": [[NSString alloc] initWithData:data
            encoding:encoding] ? : @""
    };
  } else {
    responseJSON = @{
            @"status": @0,
            @"responseHeaders": @{},
            @"responseText": [connectionError localizedDescription]
                ?: [NSNull null]
        };
      }
      // 发送响应(不会发送给相同线程的调用者)


      responseSender(@[RCTJSONStringify(responseJSON, NULL)]);
    }];
    [task resume];
  } else {
    RCTLogError(@"unsupported query type %@", queryType);
  }
}
@end

下面介绍 iOS 框架中 HTTP 的相关概念。

Web 服务

Web 浏览器使用 HTTP 协议与服务器进行通信。以一个最简单的交互为例,浏览器发送一个请求到服务器,要求获取指定 URL 的内容。服务器响应请求,并返回相应的内容(通常是 HTML 和图像),然后浏览器解析这些数据并显示页面。在更复杂的交互中,浏览器可以在请求中包含其他参数,例如表单数据。服务器处理这些参数并返回一个定制的或动态生成的 Web 页面。

Web 浏览器发展了很长时间,已经被广泛应用在各个地方。所以围绕 HTTP 有许多稳定和成熟的技术。HTTP 流量可以畅通无阻地通过大多数防火墙,Web 服务器在安全的基础上有很好的性能,并且用于 Web 应用程序的开发工具也越来越容易使用。

你可以编写一个 iOS 的客户端应用,直接利用 HTTP 协议同 Web 服务器进行通信。这个应用的服务端是一个 Web 服务。iOS 客户端和 Web 服务器通过 HTTP 协议发送请求和返回响应。

由于 HTTP 协议没有规定所传输的数据格式,所以客户端和服务器在传输中可以携带复杂的数据。这些数据通常是 JSON(JavaScript Object Notation) 或 XML 格式的。如果你拥有服务器的管理权限,那就可以在传输中使用你喜欢的任何格式。如果没有,那么客户端应用程序就必须使用服务器支持的数据格式了。

NSURL、NSURLRequest、NSURLSession 和 NSURLSessionTask

客户端应用程序从 Web 服务器获取数据所使用的是 NSURL、NSURLRequest、NSURLSession-Task、NSURLSession 这四个方便的类。

这几个类的关系是:

NSURLSessionTask === <请求

> ===> NSURLRequest(缓存策略、超时、头部


    信息、主体信息

)=== <URL> ===> NSURL(http://samplewebservice.com)

在和 Web 服务器通信过程中,这些类扮演了重要的角色:

- 一个 NSURL 实例包含 URL 格式的 Web 应用地址。对于大部分 Web 服务器来说,URL 由服务器地址、需要通信的应用程序位置,以及需要传递的参数组成。

- 一个 NSURLRequest 实例包含所有与 Web 服务器进行通信的必要数据。包括一个 NSURL 对象、缓存策略、等待服务器返回的最长时间,还有一些可以在 HTTP 协议中携带的额外数据。(NSMutableURLRequest 是 NSURLRequest 可修改的子类)。

- 一个 NSURLSessionTask 的实例封装了 NSURLRequest 的生命周期。它跟踪 NSURLRe-quest 的状态,并提供了取消、挂起和恢复操作 NSURLRequest 执行的方法。所有的会话都是 NSURLSessionTask 的子类,比较典型的是 NSURLSessionDataTask、NSURLSes-sionUploadTask、NSURLSessionDownloadTask。

- NSURLSession 实例像是一个生产 NSURLSessionTask 对象的工厂。这是一个可配置的容器,可以设置生产任务中 NSURLSessionTask 的共同属性。例如,请求头的字段、是否允许在蜂窝网络下进行连接请求。NSURLSession 还有一个丰富的委托模型,可以跟踪 NSURLSessionTask 对象的状态、处理服务器的认证要求。

构造 URL 和发送请求

Web 服务请求的格式取决于实现的 Web 服务,没有一成不变的规则,你需要查找相应的文档,然后根据指定的格式来构造请求。客户端发送给服务器的请求必须符合一定的格式,才能得到需要的响应。

我们代码中的 Web 服务需要一个 URL,可能像下面这样:

http://samplewebservice.com/courses.json

上面这个 URL 的基地址是 samplewebservice.com, Web 服务是 courses,服务器响应的数据格式是 JSON。Web 服务请求根据实际需要会有多种构成格式。上面的 Web 服务请求实际上访问了服务器端的 courses 路径,服务器端根据功能将服务分配到不同路径的方式很常见,然后要求客户端以路径作为参数请求不同的 Web 服务。因此,这个特定的 Web 服务请求的含义是:以 JSON 格式返回的所有课程数据。

另一种很常见的 Web 服务请求格式如下:

http://baseURL.com/serviceName? argumentX=valueX&argumentY=valueY

例如,可以通过构造如下的请求来指定年份和月份。

一个返回课程列表的 URL:http://samplewebservice.com/courses.json? year=2014&month=11 。

有时候,你需要将字符串进行“URL-safe”的转码。例如:空格和引号是不允许出现在 URL 中的,必须使用转义符号来代替。具体实现如下:

NSString *search = @"Play some \"Abba\"";
NSString *escaped = [search
    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// 转义后的字符串就是

"Play

你可能需要将一个转义后的字符串还原回去,NSString 中有一个方法可以做到:

- (NSString *)stringByRemovingPercentEncoding;

当请求 Web 服务的结果处理完成后,服务器就会返回包含了课程列表的 JSON 数据。

使用 NSURLSession

NSURLSession 通常会有两种定义:一种指 NSURLSession 的集合类,另一种就是指一组处理网络请求的 API。为了区分这两种定义,本书将第一种描述为 NSURLSession,第二种则描述为 NSURLSession API。

在 BNRCoursesViewController.m 类中添加一个属性,用于扩展 NSURLSession 的实例。

NSURLSessionConfiguration *config =
    [NSURLSessionConfiguration defaultSessionConfiguration];
    _session = [NSURLSession sessionWithConfiguration:config
                                              delegate:nil
                                          delegateQueue:nil];

使用 NSURLSession 创建的方法拥有三个参数:NSURLSessionConfiguration 对象、委托和委托队列。在这个应用中使用默认的参数值就可以,第一个参数的默认值需要使用 defaultSession-Configuration 来获取,第二和第三个参数传入 nil 就可以了。

现在我们实现一个方法 fetchFeed:创建一个 NSURLRequest 对象用来连接到 bookapi.bignerdranch.com 并获取课程列表。然后,使用 NSURLSession 创建一个 NSURLSessionDataTask 对象,将 NSURL-Request 请求发送到服务器。

- (void)fetchFeed
{
  NSString *requestString = @"http://bookapi.bignerdranch.com/
      courses.json";
  NSURL *url = [NSURL URLWithString:requestString];
  NSURLRequest *req = [NSURLRequest requestWithURL:url];
  NSURLSessionDataTask *dataTask =
      [self.session dataTaskWithRequest:req
                      completionHandler:^(NSData *data,
                          NSURLResponse *response, NSError *error) {
          NSString *json = [[NSString alloc]
              initWithData:data
                  encoding:NSUTF8StringEncoding];
          [dataTask resume];
          “

NSLog(@"%@", json);
  }];
  [dataTask resume];
}

创建 NSURLRequest 的过程非常简单:创建一个 NSURL 实例,然后通过 NSURL 实例化一个 NSURLRequest 对象。

NSURLSession 的功能有些不明确。NSURLSession 的作用是创建一些通用功能的任务。例如,如果您的程序需要设置通用的请求头部,那就可以通过 NSURLSession 来配置这些请求头字段。类似地,如果一组请求在蜂窝网络下需要禁止连接,那也可以通过 NSURLSession 来设置不允许手机网络进行访问,然后在创建会话时设置这些行为和属性。

NSURLSession 在一个项目中可能有多个实例,但由于 Nerdfeed 项目比较简单,所以只使用了一个实例,它将使用 sharedSession 来获取 NSURLSession 的默认配置。

为 NSURLSession 对象提供一个 NSURLRequest 对象和一个 block 对象,然后 NSURLSession 将返回一个 NSURLSessionTask 的实例。因为是从一个 Web 服务请求数据,所以创建一个 NSURLSessionTask 的子类 NSURLSessionDataTask。刚开始创建的任务处于暂停的状态,需要调用 resume 来发送 Web 服务请求。当请求完成的时候会输出返回的 JSON 数据。

JSON 数据

大家可能觉得 JSON 数据,尤其是控制台输出的 JSON 数据非常复杂,其实 JSON 的语法非常简单。

JSON 只包含几种基础的用来表示服务器数据模型的对象,例如数组、字典、字符串和数字。字典包含一个或多个键值对,其中键是字符串,而值可以是字符串、数字、数组或其他字典。数组可以包含多个字符串、数字、字典或其他数组。因此,JSON 文件就是由这些基础的类型嵌套组成的。举一个简单的 JSON 例子:

{
  "name" : "Christian",
  "friends" : ["Aaron", "Mikey"],
  "job" : {
    "company" : "Big Nerd Ranch",
    "title" : "Senior Nerd"
  }
}

JSON 中的字典通过花括号{}来表示,花括号里面是字典的键值对。JSON 文件以左花括号开始,到右花括号结束。这意味着 JSON 文件的最外层对象是一个字典,这个字典包含三个键值对 name、friends 和 job。

字符串通过引号引用文本来表示。在 JSON 的字典中,字符串既能作为键,又能作为值。最外层字典的“name”的值是字符串“Christian”。

JSON 中的数组通过中括号[]表示。数组可以包含其他 JSON 对象。还是上面的例子,字典中“friends”的值是一个字符串数组(字符串 Aaron 和 Mikey)。

JSON 中字典也可以包含其他的字典,在最外层的字典中,“job”的值就是一个有两个键值对的字典(company 和 title)。

解析 JSON 数据

Apple 提供了一些内置的类用于解析 JSON 数据,NSJSONSerialization 类可以将 JSON 对象转换为对应的 NSDictionary 对象,将 JSON 的数组转换为 NSArray,将 JSON 的字符串转换为 NSString,将 JSON 的数字转换为 NSNumber。

在你的模块中,修改 NSURLSessionDataTask 的 completionHandler,使用 NSJSONSerialization 类将 JSON 数据转化为 Objective-C 的对象。

^(NSData *data, NSURLResponse *response, NSError *error) {
    NSString *json = [[NSString alloc]
        initWithData:data
            encoding:NSUTF8StringEncoding];
    NSLog(@"%@", json);
    NSDictionary *jsonObject = [NSJSONSerialization
        JSONObjectWithData:data
                    options:0
                      error:nil];
    NSLog(@"%@", jsonObject);
}

构建并运行,然后查看控制台。您将看到 JSON 数据,在这里看到的格式略有不同,因为 NSLog 可以格式化显示词典和数组。jsonObject 是 NSDictionary 的一个实例,它有一个 NSString 类型的键和一个 NSArray 类型的关联值。NSURLSessionDataTask 完成请求时,使用 NSJSONSerialization 将 JSON 数据转换成 NSDictionary。

主线程

现在的 iOS 设备都是多核处理器,多核处理器的设备可以同时运行多个代码块,这种特性被称为并发。每个代码块运行在一个独立的线程,到现在为止,本书的所有代码都运行在主线程上。主线程有时也被称为 UI 线程,任何修改 UI 的代码都必须运行在主线程上。

当 Web 服务请求完成后,需要重新渲染视图。默认情况下,NSURLSessionDataTask 在后台线程中完成处理程序。需要使用一种方法让代码运行在主线程以重新渲染视图,在这里可以轻松地使用 dispatch_async 函数来完成。

在 BNRCoursesViewController.m 文件中,更新处理程序。在主线程中执行代码来重新渲染视图。

^(NSData *data, NSURLResponse *response, NSError *error) {
    NSDictionary *jsonObject = [NSJSONSerialization
        JSONObjectWithData:data
                    options:0
                      error:nil];
    NSArray *courses = jsonObject[@"courses"];
    “

NSLog(@"%@", courses);
    dispatch_async(dispatch_get_main_queue(), ^{
        // working with data courses here
    });
}

构建并运行,当网络请求完成后,就可以看到服务器返回的课程列表。

更深入的学习:请求主体

当 NSURLSessionTask 和 Web 服务器进行通信时,使用的就是 HTTP 协议。HTTP 协议规定:发送和接收的任何数据必须遵循 HTTP 规范。

NSURLRequest 有许多方法,用来设置请求的各个参数,生成遵循 HTTP 规范的格式。

HTTP 请求分为请求行、请求头和请求主体三部分。HTTP 请求主体是可选的。请求行(在 Apple 中称为状态行)是 HTTP 请求的第一行,用于告诉服务器客户端想做什么事情。在当前例子的请求中,客户端想要通过 GET 方法获取到/courses.json 的数据(除此之外,请求还标明了当前使用的 HTTP 版本)。

在这里 GET 命令是一种 HTTP 的请求方法。虽然 HTTP 规定了好几种请求方法,但最常用的还是 GET 和 POST。NSURLRequest 默认会使用 GET 方法,GET 方法表示客户端想要从服务器获得一些信息。信息所在的位置称为 Request-URI(/courses.json)。

在比较早的网络时代,Request-URI 通常是文件在服务器上的路径。

例如,请求 httpy/ivww.website.com/index.html 会返回文件 index.html,然后由浏览器在其窗口中显示该文件。现在,也可以用 Request-URI 来表示服务器实现的某个 Web 服务。例如,本章的 Nerdfeed 会向 courses 服务发送请求,提供相应的参数并得到服务器返回的 JSON 数据。同样是通过 GET 方法获取信息,但是服务器能够根据传入的参数执行更多的任务。

除了从服务器请求信息外,还可以向服务器发送信息。例如,许多 Web 服务允许上传照片。客户端将照片数据通过向服务器发送请求的方式传给服务器,在这种情况下,就需要使用 HTTP POST 方法了。POST 方法表示客户端想要向服务器发送一些信息,这些信息包含在可选的 HTTP 主体中。这些数据包括 XML、JSON 或 Base64 编码格式。如果请求包含主体,那请求头就必须包含 Content-Length 字段。NSURLRequest 对象会自动计算主体的大小并添加 Content-Length 字段。代码如下:

NSURL *someURL = [NSURL URLWithString:@"http://www.photos.com/
    upload"];
UIImage *image = [self profilePicture];
“

NSData *data = UIImagePNGRepresentation(image);
NSMutableURLRequest *req = [NSMutableURLRequest
        requestWithURL:someURL
            cachePolicy:NSURLRequestReloadIgnoringCacheData
      timeoutInterval:90];
// 添加

 HTTP 主体数据,会自动设置

 Content-Length 请求头


req.HTTPBody = data;
// 这一行会修改请求中的

 HTTP 方法


req.HTTPMethod = @"POST";
// 也可以自己来设置

 Content-Length 请求头


[req setValue:[NSString stringWithFormat:@"%d", data.length]
    forHTTPHeaderField:@"Content-Length"];

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。