NSURLConnection 网络请求

前言

  • Objective-C

    1
    2
    3
    @interface NSURLConnection : NSObject

    DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection
  • Swift

    1
    class NSURLConnection : NSObject
  • 从 iOS 9 开始 NSURLConnection 的大部分方法被废弃。

1、NSURLConnection

  • NSURLConnection 提供了两种方式来实现连接,一种是同步的另一种是异步的,异步的连接将会创建一个新的线程,这个线程将会来负责下载的动作。而对于同步连接,在下载连接和处理通讯时,则会阻塞当前调用线程。

  • 许多开发者都会认为同步的连接将会堵塞主线程,其实这种观点是错误的。一个同步的连接是会阻塞调用它的线程。如果你在主线程中创建一个同步连接,没错,主线程会阻塞。但是如果你并不是从主线程开启的一个同步的连接,它将会类似异步的连接一样。因此这种情况并不会堵塞你的主线程。事实上,同步和异步的主要区别就是运行 runtime 为会异步连接创建一个线程,而同步连接则不会。

1.1 NSURLConnection 的常用类

  • 1、NSURL:请求地址;

  • 2、NSURLRequest:封装一个请求,保存发给服务器的全部数据,包括一个 NSURL 对象,请求方法、请求头、请求体 ….;

  • 3、NSMutableURLRequest:NSURLRequest 的子类

  • 4、NSURLConnection:负责发送请求,建立客户端和服务器的连接。发送 NSURLRequest 的数据给服务器,并收集来自服务器的响应数据。

1.2 使用 NSURLConnection 发送请求的步骤

  • 1、创建一个 NSURL 对象,设置请求路径(设置请求路径);

  • 2、传入 NSURL 创建一个 NSURLRequest 对象,设置请求头和请求体(创建请求对象);

    • 任何 NSURLRequest 默认都是 GET 请求。
  • 3、使用 NSURLConnection 发送 NSURLRequest(发送请求)。

    • 发送同步请求:有返回值。
    • 发送异步请求:没有返回值。

1.3 发送同步请求

  • 使用 NSURLConnection 的 sendSynchronousRequest:returningResponse:error: 类方法,我们可以进行同步请求。

  • 在创建一个同步的网络连接的时候我们需要明白一点,并不是是我们的这个同步连接一定会堵塞我们的主线程,如果这个同步的连接是创建在主线程上的,那么这种情况下是会堵塞我们的主线程的,其他的情况下是不一定会堵塞我们的主线程的。例如如果在 GCD 的全局并发队列上初始化了一个同步的连接,其实并不会堵塞我们的主线程的。

1.4 发送异步请求

  • 发送异步请求有两种方式:

    • 1)使用 block 回调:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      NS_AVAILABLE(10_7, 5_0)
      + (void)sendAsynchronousRequest:(NSURLRequest*) request
      queue:(NSOperationQueue*) queue
      completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler;

      创建一个操作,放在 NSOperation 队列中执行,默认是异步执行的。当服务器有返回数据的时候调用会开一条新的线程去发送请求,主线程继续往下走,
      当拿到服务器的返回数据的数据的时候再回调 block,执行 block 代码段。这种情况不会卡住主线程。

      queue 队列的作用是决定这个 block 操作放在哪个线程执行?刷新 UI 界面的操作应该放在主线程执行,不能放在子线程,在子线程处理 UI 相关操作
      会出现一些莫名的问题。使用 [NSOperationQueue mainQueue] 返回一个和主线程相关的队列,即主队列,这个 block 操作会被放在主线程中执行。使用
      [[NSOperationQueue alloc] init] 返回的不是主队列,这个 block 操作不会被放在主线程中执行。
    • 2)代理:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      - (instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;
      + (NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;

      要监听服务器返回的 data,所以使用 <NSURLConnectionDataDelegate> 协议。

      当接收到服务器的响应(连通了服务器)时会调用:
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

      当接收到服务器的数据时会调用(可能会被调用多次,每次只传递部分数据,需要拼接接收到的所有数):
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

      当服务器的数据加载完毕时就会调用:
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection

      请求错误(失败)的时候调用(请求超时\断网\没有网\,一般指客户端错误):
      - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

      NSURLConnection 的代理方法默认都是在主线程上执行的,会对界面产生卡顿。

      For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
      为了让连接工作正常,调用线程的运行循环必须在默认的运行循环模式下。

      如果要让 NSURLConnection 实现在后台线程回调代理方法,需要在后台线程启动 NSURLConnection,并启动后台线程的运行循环,NSURLConnection
      执行完毕后,会自动停止后台线程的运行循环。

      启动子线程的运行循环方法:
      CFRunLoopRun();
      [[NSRunLoop currentRunLoop] run]; // NSRunLoop 只能启动,没有提供停止的接口

1.5 开始/停止网络连接

1
2
- (void)start;
- (void)cancel;
  • 创建网络连接后可以不使用 start,系统会自动开始网络连接。

  • 取消一个请求后,连接不在调用代理方法,如果希望再此连接,需要再次创建一个新的网络连接。

1.6 NSURLConnectionDownloadDelegate

  • NSURLConnectionDownloadDelegate 代理方法是为 Newsstand Kit’s(杂志包) 创建的下载服务的,Newsstand 主要在国外使用比较广泛,国内极少。
  • 如果使用 NSURLConnectionDownloadDelegate 代理方法监听下载进度,能够监听到进度,但是找不到下载的文件。

2、NSURLConnection 同步 GET 请求

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 设置网络接口
    NSString *urlStr = @"http://192.168.88.200:8080/MJServer/video?type=JSON";

    // 设置请求路径
    NSURL *url = [NSURL URLWithString:urlStr];

    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    // 创建同步网络请求
    NSData *syncNetData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:NULL error:NULL];
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 设置网络接口
    let urlStr = "http://192.168.88.200:8080/MJServer/video?type=JSON"

    // 设置请求路径
    let url = NSURL(string: urlStr!)

    let urlRequest = NSURLRequest(URL: url!)

    // 创建同步网络请求
    let syncNetData = try? NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil)

3、NSURLConnection 同步 POST 请求

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 设置网络接口
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];

    // 创建请求对象
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];

    // 设置请求方式,默认为 GET 请求
    urlRequest.HTTPMethod = @"POST";

    // 设置请求体(请求参数)
    urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding];

    // 创建同步网络请求
    NSData *syncNetData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:NULL error:NULL];
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 设置网络接口
    let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video")

    // 创建请求对象
    let urlRequest = NSMutableURLRequest(URL: url!)

    // 设置请求方式,默认为 GET 请求
    urlRequest.HTTPMethod = "POST";

    // 设置请求体(请求参数)
    urlRequest.HTTPBody = "type=JSON".dataUsingEncoding(NSUTF8StringEncoding)

    // 创建同步网络请求
    let syncNetData = try? NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil)

4、NSURLConnection 异步 GET 请求

  • Objective-C

    • 使用 block 回调方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 设置网络接口
      NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=XML"];

      // 创建请求对象
      NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

      // 创建异步网络请求
      [NSURLConnection sendAsynchronousRequest:urlRequest
      queue:[NSOperationQueue mainQueue]
      completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

      if (connectionError == nil && data != nil) {

      }
      }];
    • 使用 协议 方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      // 遵守协议 <NSURLConnectionDataDelegate>

      @property(nonatomic, retain)NSMutableData *asyncNetData;

      // 设置网络接口
      NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=XML"];

      // 创建请求对象
      NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

      // 创建异步网络请求
      [NSURLConnection connectionWithRequest:urlRequest delegate:self];

      // 协议方法

      // 接收到服务器的响应
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

      // 异步下载数据源初始化
      self.asyncNetData = [[NSMutableData alloc] init];
      }

      // 接收到服务器数据
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

      // 拼接从服务器下载的数据
      [self.asyncNetData appendData:data];
      }

      // 服务器的数据加载完毕
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection {

      // 处理从服务器下载的数据
      self.textView.text = [[NSString alloc] initWithData:self.asyncNetData encoding:NSUTF8StringEncoding];
      }

      // 请求错误
      - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

      }
  • Swift

    • 使用 闭包 方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 设置网络接口
      let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video?type=XML")

      // 创建请求对象
      let urlRequest = NSURLRequest(URL: url!)

      // 创建异步网络请求
      NSURLConnection.sendAsynchronousRequest(urlRequest,
      queue: NSOperationQueue.mainQueue())
      { (response:NSURLResponse?, data:NSData?, connectionError:NSError?) -> Void in

      if connectionError == nil {

      // 服务器的数据加载完毕,处理从服务器下载的数据
      }
      }
    • 使用 协议 方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      // 遵守协议 NSURLConnectionDataDelegate

      // 设置网络接口
      let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video?type=XML")

      // 创建请求对象
      let urlRequest = NSURLRequest(URL: url!)

      // 创建异步网络请求
      NSURLConnection(request: urlRequest, delegate: self)

      // 协议方法

      // 接收到服务器的响应
      func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {

      // 异步下载数据源初始化
      self.asyncNetData = NSMutableData()
      }

      // 接收到服务器数据
      func connection(connection: NSURLConnection, didReceiveData data: NSData) {

      // 拼接从服务器下载的数据
      self.asyncNetData?.appendData(data)
      }

      // 服务器的数据加载完毕
      func connectionDidFinishLoading(connection: NSURLConnection) {

      // 处理从服务器下载的数据
      self.textView.text = NSString(data: self.asyncNetData!, encoding: NSUTF8StringEncoding) as! String
      }

      // 请求错误
      func connection(connection: NSURLConnection, didFailWithError error: NSError) {

      }

5、NSURLConnection 异步 POST 请求

  • Objective-C

    • 使用 block 回调方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      // 设置网络接口
      NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];

      // 创建请求对象
      NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];

      // 设置请求方式,默认为 GET 请求
      urlRequest.HTTPMethod = @"POST";

      // 设置请求体(请求参数)
      urlRequest.HTTPBody = [@"type=XML" dataUsingEncoding:NSUTF8StringEncoding];

      // 创建异步网络请求
      [NSURLConnection sendAsynchronousRequest:urlRequest
      queue:[NSOperationQueue mainQueue]
      completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

      if (connectionError == nil && data != nil) {

      }
      }];
    • 使用 协议 方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      // 遵守协议 <NSURLConnectionDataDelegate>

      // 设置网络接口
      NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"];

      // 创建请求对象
      NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];

      // 设置请求方式,默认为 GET 请求
      urlRequest.HTTPMethod = @"POST";

      // 设置请求体(请求参数)
      urlRequest.HTTPBody = [@"type=XML" dataUsingEncoding:NSUTF8StringEncoding];

      // 创建异步网络请求
      [NSURLConnection connectionWithRequest:urlRequest delegate:self];

      // 协议方法

      // 已经发送请求体
      - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten
      totalBytesWritten:(NSInteger)totalBytesWritten
      totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {

      }

      // 接收到服务器的响应
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

      }

      // 接收到服务器数据
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

      }

      // 服务器的数据加载完毕
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection {

      }

      // 请求错误
      - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

      }
  • Swift

    • 使用 闭包 回调方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // 设置网络接口
      let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video")

      // 创建请求对象
      let urlRequest = NSMutableURLRequest(URL: url!)

      // 设置请求方式,默认为 GET 请求
      urlRequest.HTTPMethod = "POST"

      // 设置请求体(请求参数)
      urlRequest.HTTPBody = "type=XML".dataUsingEncoding(NSUTF8StringEncoding)

      // 创建异步网络请求
      NSURLConnection.sendAsynchronousRequest(urlRequest,
      queue: NSOperationQueue.mainQueue())
      { (response:NSURLResponse?, data:NSData?, connectionError:NSError?) in

      }
    • 使用 协议 方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      // 遵守协议 <NSURLConnectionDataDelegate>

      // 设置网络接口
      let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video")

      // 创建请求对象
      let urlRequest = NSMutableURLRequest(URL: url!)

      // 设置请求方式,默认为 GET 请求
      urlRequest.HTTPMethod = "POST"

      // 设置请求体(请求参数)
      urlRequest.HTTPBody = "type=XML".dataUsingEncoding(NSUTF8StringEncoding)

      // 创建异步网络请求
      NSURLConnection(request: urlRequest, delegate: self)

      // 协议方法

      // 已经发送请求体
      func connection(connection: NSURLConnection, didSendBodyData bytesWritten: Int,
      totalBytesWritten: Int,
      totalBytesExpectedToWrite: Int) {

      }

      // 接收到服务器的响应
      func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {

      }

      // 接收到服务器数据
      func connection(connection: NSURLConnection, didReceiveData data: NSData) {

      }

      // 服务器的数据加载完毕
      func connectionDidFinishLoading(connection: NSURLConnection) {

      }

      // 请求错误
      func connection(connection: NSURLConnection, didFailWithError error: NSError) {

      }

6、NSURLConnection 文件下载

6.1 获取文件信息

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // 使用 HEAD 请求方式
    request.HTTPMethod = @"HEAD";

    NSURLResponse *response = nil;
    NSError *error = nil;

    // 使用同步请求方式,后续的下载会依赖于此
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    if (error == nil && response != nil) {

    // 获取文件大小或名称
    NSLog(@"要下载文件的长度 %tu", response.expectedContentLength);
    }

6.2 使用 GET 数据请求方式下载,文件句柄存储

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength;

    // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength;

    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    // 协议方法

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

    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]
    stringByAppendingPathComponent:@"321.mp4"];

    // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL];

    // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength;

    self.recvedfileLength = 0;
    }

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

    // 将从服务器下载的数据直接写入文件
    [self writeToFile:data];

    // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length;

    // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    }

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

    }

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

    }

    // 将数据写入文件
    - (void)writeToFile:(NSData *)data {

    /*
    NSFileManager:文件管理器,文件复制,删除,是否存在...操作,类似于在 Finder 中进行的操作
    NSFileHandle :文件 "句柄(指针)" Handle 操纵杆,凡事看到 Handle 单词,表示对前面一个名词(File)的操作,
    对一个文件进行独立的操作。
    */

    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]
    stringByAppendingPathComponent:@"321.mp4"];

    // 打开文件,如果文件不存在,fp == nil
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:documentsPath];

    // 如果文件不存在
    if (fp == nil) {

    // 将数据直接写入文件
    [data writeToFile:documentsPath atomically:YES];
    } else {

    // 将文件指针移动到文件末尾
    [fp seekToEndOfFile];

    // 写入数据
    [fp writeData:data];

    // 关闭文件,C 语言中有一个默认的约定,凡事打开文件,都必须关闭
    [fp closeFile];
    }
    }

6.3 使用 GET 数据请求方式下载,文件输出流存储

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength;

    // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength;

    // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream;

    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    // 协议方法

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

    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]
    stringByAppendingPathComponent:@"312.mp4"];

    // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL];

    // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:documentsPath append:YES];

    // 打开文件流
    [self.fileStream open];

    // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength;

    self.recvedfileLength = 0;
    }

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

    // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length];

    // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length;

    // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    }

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

    // 关闭文件流
    [self.fileStream close];
    }

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

    // 关闭文件流
    [self.fileStream close];
    }

6.4 使用 专用下载 方式

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    // 遵守协议 <NSURLConnectionDownloadDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    // 协议方法

    - (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten
    totalBytesWritten:(long long)totalBytesWritten
    expectedTotalBytes:(long long)expectedTotalBytes {

    /*
    下载进度:

    bytesWritten 本次下载子节数
    totalBytesWritten 已经下载字节数
    expectedTotalBytes 总下载字节数(文件总大小)
    */

    float progress = (float)totalBytesWritten / expectedTotalBytes;
    NSLog(@"%f", progress);
    }

    - (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL {

    /*
    destinationURL 下载保存的路径,下载完成之后,找不到下载的文件。
    */

    NSLog(@"%@", destinationURL);
    }

6.5 断点续传下载

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength;

    // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength;

    // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream;

    // 目标目录
    @property (nonatomic, copy) NSString *targetPath;

    @property (nonatomic, strong) NSURLConnection *conn;

    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];

    // 检查服务器上的文件信息
    [self checkServerFileInfoWithURL:url];

    // 检查本地文件信息
    long long fileSize = [self checkLocalFileInfo];

    // 文件已经下载到本地
    if (fileSize == self.expectedContentLength) {

    return;
    }

    // 根据本地文件的长度,从对应 "偏移" 位置开始下载
    [self downloadWithURL:url offset:fileSize];

    // 检查服务器上的文件信息
    - (void)checkServerFileInfoWithURL:(NSURL *)url {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"HEAD";

    NSURLResponse *response = nil;
    NSError *error = nil;

    // 发送同步方法
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    if (error == nil && response != nil) {

    // 文件大小
    self.expectedContentLength = response.expectedContentLength;

    // 文件名,保存到临时文件夹
    self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
    }
    }

    // 检查本地文件信息
    - (long long)checkLocalFileInfo {

    long long fileSize = 0;

    // 检查本地是否存在文件
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.targetPath]) {

    NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.targetPath error:NULL];

    // 获取文件大小
    fileSize = [dict fileSize];
    }

    // 判断是否比服务器的文件大
    if (fileSize > self.expectedContentLength) {

    // 删除文件
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    fileSize = 0;
    }

    return fileSize;
    }

    // 从断点处开始下载
    - (void)downloadWithURL:(NSURL *)url offset:(long long)offset {

    // 记录本地文件大小
    self.recvedfileLength = offset;

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url
    cachePolicy:1
    timeoutInterval:15];

    // 一旦设置了 Range,response 的状态码会变成 206
    [urlRequest setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"];

    // 遵守协议 <NSURLConnectionDataDelegate>
    self.conn = [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    [self.conn start];
    }

    // 暂停下载

    - (void)pause1 {

    [self.conn cancel];
    }

    // 协议方法

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

    // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.targetPath append:YES];
    [self.fileStream open];
    }

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

    // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length];

    // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length;

    // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    }

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

    [self.fileStream close];
    }

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

    // 关闭文件流
    [self.fileStream close];
    }

6.6 异步下载

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength;

    // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength;

    // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream;

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

    // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    // 启动运行循环
    CFRunLoopRun();
    });

    // 协议方法

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

    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
    NSUserDomainMask,
    YES)[0]
    stringByAppendingPathComponent:@"312.mp4"];

    // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL];

    // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:documentsPath append:YES];

    // 打开文件流
    [self.fileStream open];

    // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength;

    self.recvedfileLength = 0;
    }

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

    // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length];

    // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length;

    // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    }

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

    // 关闭文件流
    [self.fileStream close];
    }

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

    // 关闭文件流
    [self.fileStream close];
    }

7、NSURLConnection 下载单例封装

  • QDownloaderOperation.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @interface QDownloaderOperation : NSOperation

    /// 类方法
    + (instancetype)downloaderWithURL:(NSURL *)url
    progress:(void (^)(float progress))progress
    successed:(void (^)(NSString *targetPath))successed
    failed:(void (^)(NSError *error))failed;

    /// 暂停当前下载
    - (void)pauseDownload;

    /// 取消当前下载
    - (void)cancelDownload;

    @end
  • QDownloaderOperation.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    @interface QDownloaderOperation () <NSURLConnectionDataDelegate>

    /// 下载文件总长度
    @property (nonatomic, assign) long long expectedContentLength;

    /// 已下载文件大小
    @property (nonatomic, assign) long long recvedfileLength;

    /// 下载目标目录
    @property (nonatomic, copy) NSString *targetPath;

    /// 下载文件输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream;

    /// block 属性
    @property (nonatomic, copy) void (^progressBlock)(float);
    @property (nonatomic, copy) void (^successedBlock)(NSString *);
    @property (nonatomic, copy) void (^failedBlock)(NSError *);

    /// 网络连接属性
    @property (nonatomic, strong) NSURLConnection *conn;
    @property (nonatomic, strong) NSURL *downloadURL;

    @end

    + (instancetype)downloaderWithURL:(NSURL *)url
    progress:(void (^)(float))progress
    successed:(void (^)(NSString *))successed
    failed:(void (^)(NSError *))failed {

    QDownloaderOperation *downloader = [[self alloc] init];

    downloader.progressBlock = progress;
    downloader.successedBlock = successed;
    downloader.failedBlock = failed;

    downloader.downloadURL = url;

    return downloader;
    }

    - (void)main {
    @autoreleasepool {

    // 检查服务器上的文件信息
    [self checkServerFileInfoWithURL:self.downloadURL];

    if (self.isCancelled) return;

    // 检查本地文件信息
    long long fileSize = [self checkLocalFileInfo];

    if (fileSize == self.expectedContentLength) {

    // 下载完成的回调
    if (self.successedBlock) {
    dispatch_async(dispatch_get_main_queue(), ^{
    self.successedBlock(self.targetPath);
    });

    // 下载进度的回调
    if (self.progressBlock) {
    self.progressBlock(1.0);
    }
    }
    return;
    }

    // 根据本地文件的长度,从对应 "偏移" 位置开始下载
    [self downloadWithURL:self.downloadURL offset:fileSize];
    }
    }

    - (void)pauseDownload {

    // 取消一个请求,调用此方法后,连接不在调用代理方法
    [self.conn cancel];
    }

    - (void)cancelDownload {

    [self.conn cancel];
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    }

    /// 检查服务器上的文件信息

    - (void)checkServerFileInfoWithURL:(NSURL *)url {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"HEAD";

    NSURLResponse *response = nil;
    NSError *error = nil;

    // 发送同步方法
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    if (error == nil && response != nil) {

    // 文件大小
    self.expectedContentLength = response.expectedContentLength;

    // 文件名
    self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
    }
    }

    /// 检查本地文件信息

    - (long long)checkLocalFileInfo {

    long long fileSize = 0;

    // 检查本地是否存在文件
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.targetPath]) {
    NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.targetPath error:NULL];

    // 获取文件大小
    fileSize = [dict fileSize];
    }

    // 判断是否比服务器的文件大
    if (fileSize > self.expectedContentLength) {

    // 删除文件
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    fileSize = 0;
    }

    return fileSize;
    }

    /// 从断点处开始下载

    - (void)downloadWithURL:(NSURL *)url offset:(long long)offset {

    // 记录本地文件大小
    self.recvedfileLength = offset;

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:15];

    // 一旦设置了 Range,response 的状态码会变成 206
    [urlRequest setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"];

    // 遵守协议 <NSURLConnectionDataDelegate>
    self.conn = [NSURLConnection connectionWithRequest:urlRequest delegate:self];

    [self.conn start];

    // 开启子线程运行循环
    CFRunLoopRun();
    }

    /// 协议方法

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

    // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.targetPath append:YES];

    // 打开文件流
    [self.fileStream open];
    }

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

    // 将数据的 "字节" 一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length];

    // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length;

    // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;

    // 进度的回调
    if (self.progressBlock) {
    self.progressBlock(progress);
    }
    }

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

    // 关闭文件流
    [self.fileStream close];

    // 完成的回调
    if (self.successedBlock) {

    // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
    self.successedBlock(self.targetPath);
    });
    }
    }

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

    // 关闭文件流
    [self.fileStream close];

    // 失败的回调
    if (self.failedBlock) {
    self.failedBlock(error);
    }
    }
  • QDownloaderManager.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @interface QDownloaderManager : NSObject

    /// 单例
    + (instancetype)sharedManager;

    /// 开始下载
    - (void)downloadWithURL:(NSURL *)url progress:(void (^)(float progress))progress
    successed:(void (^)(NSString *targetPath))successed
    failed:(void (^)(NSError *error))failed;

    /// 暂停下载
    - (void)pauseWithURL:(NSURL *)url;

    /// 取消下载
    - (void)cancelWithURL:(NSURL *)url;

    @end
  • QDownloaderManager.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    @interface QDownloaderManager ()

    /// 下载操作缓冲池
    @property (nonatomic, strong) NSMutableDictionary *downloadCache;

    /// 下载操作队列
    @property (nonatomic, strong) NSOperationQueue *downloadQueue;

    @end

    + (instancetype)sharedManager {
    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    instance = [[self alloc] init];
    });

    return instance;
    }

    - (void)downloadWithURL:(NSURL *)url progress:(void (^)(float))progress
    successed:(void (^)(NSString *))successed
    failed:(void (^)(NSError *))failed {

    // 检查缓冲池中是否有下载,如果有,直接返回
    if (self.downloadCache[url.absoluteString] != nil) {
    NSLog(@"正在在玩命下载中...");
    return;
    }

    QDownloaderOperation *downloader = [QDownloaderOperation downloaderWithURL:url
    progress:progress
    successed:^(NSString *targetPath) {

    // 删除下载操作
    [self.downloadCache removeObjectForKey:url.absoluteString];

    if (successed != nil) {
    successed(targetPath);
    }

    } failed:^(NSError *error) {

    [self.downloadCache removeObjectForKey:url.absoluteString];

    if (failed != nil) {
    failed(error);
    }
    }];

    // 添加到缓冲池
    [self.downloadCache setObject:downloader forKey:url.absoluteString];

    // 添加到队列
    [self.downloadQueue addOperation:downloader];
    }

    - (void)pauseWithURL:(NSURL *)url {

    // 判断缓冲池中是否有对应的下载操作
    QDownloaderOperation *downloader = self.downloadCache[url.absoluteString];

    if (downloader != nil) {

    // 暂停 downloader 内部的 NSURLConnection
    [downloader pauseDownload];

    // 给操作发送取消消息 NSOperation
    [downloader cancel];

    // 从缓冲池中清除
    [self.downloadCache removeObjectForKey:url.absoluteString];
    }
    }

    - (void)cancelWithURL:(NSURL *)url {

    // 判断缓冲池中是否有对应的下载操作
    QDownloaderOperation *downloader = self.downloadCache[url.absoluteString];

    if (downloader != nil) {

    // 取消 downloader 内部的 NSURLConnection
    [downloader cancelDownload];

    // 给操作发送取消消息 NSOperation
    [downloader cancel];

    // 从缓冲池中清除
    [self.downloadCache removeObjectForKey:url.absoluteString];
    }
    }

    /// 懒加载

    - (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
    _downloadCache = [NSMutableDictionary dictionary];
    }
    return _downloadCache;
    }

    - (NSOperationQueue *)downloadQueue {
    if (_downloadQueue == nil) {
    _downloadQueue = [[NSOperationQueue alloc] init];

    // 设置最大并发数
    _downloadQueue.maxConcurrentOperationCount = 5;
    }
    return _downloadQueue;
    }
  • ViewController.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 下载进度
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;

    @property (nonatomic, strong) NSURL *url;

    // 开始下载

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    self.url = url;

    [[QDownloaderManager sharedManager] downloadWithURL:url progress:^(float progress) {

    dispatch_async(dispatch_get_main_queue(), ^{

    self.progressView.progress = progress;
    });

    } successed:^(NSString *targetPath) {

    NSLog(@"%@", targetPath);

    } failed:^(NSError *error) {

    NSLog(@"%@", error);
    }];

    // 暂停下载

    [[QDownloaderManager sharedManager] pauseWithURL:self.url];

    // 取消下载

    [[QDownloaderManager sharedManager] cancelWithURL:self.url];
文章目录
  1. 1. 前言
  2. 2. 1、NSURLConnection
    1. 2.1. 1.1 NSURLConnection 的常用类
    2. 2.2. 1.2 使用 NSURLConnection 发送请求的步骤
    3. 2.3. 1.3 发送同步请求
    4. 2.4. 1.4 发送异步请求
    5. 2.5. 1.5 开始/停止网络连接
    6. 2.6. 1.6 NSURLConnectionDownloadDelegate
  3. 3. 2、NSURLConnection 同步 GET 请求
  4. 4. 3、NSURLConnection 同步 POST 请求
  5. 5. 4、NSURLConnection 异步 GET 请求
  6. 6. 5、NSURLConnection 异步 POST 请求
  7. 7. 6、NSURLConnection 文件下载
    1. 7.1. 6.1 获取文件信息
    2. 7.2. 6.2 使用 GET 数据请求方式下载,文件句柄存储
    3. 7.3. 6.3 使用 GET 数据请求方式下载,文件输出流存储
    4. 7.4. 6.4 使用 专用下载 方式
    5. 7.5. 6.5 断点续传下载
    6. 7.6. 6.6 异步下载
  8. 8. 7、NSURLConnection 下载单例封装
隐藏目录