【问题标题】:Why does NSURLConnection timeout when sending many requests?为什么发送多个请求时 NSURLConnection 超时?
【发布时间】:2012-01-27 20:39:40
【问题描述】:

我正在为 iOS 应用编写网络类。此类将处理所有日志记录和网络流量。我有一个问题,我必须一次发送可能数千个请求,但是 NSURLConnections 超时,因为在所有 NSURLConnections 启动之前不会调用委托方法,此时超时期限已过期。我正在为 Drupal 使用一个 rest API,不幸的是,我不知道用一个请求创建多个实例的方法。如何在发送回复的同时接收回复?如果我使用 GCD 来传递 NSURLConnections 的创建,那会解决问题吗?我想我必须传递遍历对象以发送和发送到 GCD 的整个操作,以释放主线程来响应响应。

-(BOOL)sendOperation:(NetworkOperation)op 
     NetworkDataType:(NetworkDataType)dataType
          JsonToSend:(NSArray *)json
          BackupData:(NSArray *)data
{
    if(loggingMode)
    {
        return YES;
    }
    NSURLConnection *networkConnection;
    NSData *send;
    NSString *uuid = [self generateUUID];
    NSMutableArray *connections = [[NSMutableArray alloc] init];
    NSMutableURLRequest *networkRequest;
    for (int i=0; i<[json count] && (data ? i<[data count] : YES); i++) 
    {
        if(op == Login)
        {
            /*Grab all cookies from the server domain and delete them, this prevents login failure
             because user was already logged in. Probably find a better solution like recovering
             from the error*/
            NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:
                                [[NSURL alloc] initWithString:networkServerAddress]];
            for (NSHTTPCookie *cookie in cookies) 
            {
                [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
            }
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                          [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/user/login"]]];
        }
        else if(op == StartExperiment)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/node"]]];
        }
        else if(op == Event || op == EndExperiment || op == SendAll)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/node"]]];
        }
        else if(op == Logout)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/user/logout"]]];
        }

        send = [[json objectAtIndex:i] dataUsingEncoding:NSUTF8StringEncoding];
        //Set the headers appropriately
        [networkRequest setHTTPMethod:@"POST"];  
        [networkRequest setValue:@"application/json"    
              forHTTPHeaderField: @"Content-type"];
        [networkRequest setValue:[NSString stringWithFormat:@"%d", [send length]]  
              forHTTPHeaderField:@"Content-length"]; 
        [networkRequest setValue:@"application/json"    
              forHTTPHeaderField:@"Accept"];
        //Set the body to the json encoded string
        [networkRequest setHTTPBody:send]; 
        //Starts async request
        networkConnection = [[NSURLConnection alloc] initWithRequest:networkRequest delegate:self];
        //Successfully created, we are off
        if(networkConnection)
        {
            [networkConnectionsAndData setValue:[[NSMutableArray alloc] initWithObjects:uuid,
                                                 [[NSNumber alloc] initWithInt:op], [[NSNumber alloc] initWithInt:dataType], [[NSMutableData alloc] init], (data ? [data objectAtIndex:i] : [NSNull null]), nil]
                                         forKey:[networkConnection description]];
        }
        else    //Failed to conn ect
        {
            NSLog(@"Failed to create NSURLConnection");
            return NO;
        }
    }
    [[self networkOperationAndConnections] setObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:[[NSMutableArray alloc] initWithObjects:connections, nil], @"connections", [[NSMutableArray alloc] init], @"errors", nil]
                                              forKey:uuid];
    return YES;
}

字典用于跟踪与每个 NSURLConnection 相关的数据,并将 NSURLConnection 组合成一组以确定整个操作的最终成功或失败。

更新

AFNetworking 是完成这个项目的关键。它不仅大幅清理了代码,而且处理了发送这么多请求时继承的所有线程问题。更不用说使用 AFNetworking,我可以将所有请求批处理到一个操作中。使用块,就像 AFNetworking 使用的那样,是比 NSURLConnections 的标准委托更清洁和更好的解决方案。

【问题讨论】:

    标签: objective-c ios timeout nsurlconnection grand-central-dispatch


    【解决方案1】:

    您肯定需要允许 NSURLRequest / Connection 在另一个线程上运行。 (不是主线程!)

    为清晰起见进行了编辑**: 我注意到您对“//Starts async request”的评论,我想确保您意识到您的呼叫不是典型的“异步”功能所期望的。实际上它只是同步触发请求,但由于它是一个 Web 请求,它本质上是异步的。您实际上希望将这些请求放在另一个线程上以实现完全异步行为。

    除此之外,我真的建议在这里深入研究 Apple 的网络示例项目:MVCNetworking

    至于您的问题的具体细节,有几种方法可以做到这一点。

    1. 一种方法是使用initWithRequest:&lt;blah&gt; delegate:&lt;blah&gt; startImmediately:FALSE 阻止您的连接立即启动,然后使用scheduleInRunLoop:forMode: 在另一个线程的运行循环中安排您的NSURLConnection 实例
      • (注意:然后您必须通过调用 start 来启动连接 - 最好通过 NSOperation + NSOperationQueue 执行此操作。)
    2. 或在NSURLConnection 上使用此静态方法来创建/启动连接,而不是执行分配/初始化:sendAsynchronousRequest:queue:completionHandler:
      • (注意:此方法的效果与上述几乎相同,但会混淆细节并让您无法控制。)

    老实说,我上面的快速回答不足以完成此类项目,您需要做一些研究来填补空白,尤其是对于 NSOperationQueue,这就是 MVCNetworking 项目会帮助你的。

    网络连接是一只善变的野兽——即使它们在后台线程上运行,您也可以通过尝试同时执行太多工作来超时并终止连接!我会认真重新考虑一次打开数千个 NSURLConnections,使用 NSOperationQueue 将有助于解决这个问题。

    其他资源: 这是一个第 3 方库,可以让您的网络冒险不那么痛苦:

    【讨论】:

    • 我的印象是,当它初始化时,它正在那里启动一个异步请求。所以除非你使用sendAsynchronousRequest,否则它不是异步请求?感谢您的快速答复。
    • 对不起,我不是很清楚那里。编辑如下:我注意到您对“//Starts async request”的评论,我想确保您意识到您的呼叫不是典型的“异步”功能所期望的。实际上它只是同步触发请求,但由于它是一个 Web 请求,它本质上是异步的。您实际上希望将这些请求放在另一个线程上以实现完全异步行为。
    • 添加了指向我在此处推荐的第三方库的链接,可能会对您有所帮助
    • 如果我只是将请求的开始移动到不同的线程会有很大的收获吗?我觉得只是 init/alloc 那里的连接与 sendAsyncRequest 基本相同,它产生了它的线程来进行实际的网络连接,但主线程仍然太忙而无法响应委托方法。 sendAsyncRequest 辅助方法真的减轻了很多工作吗?为了获得最佳性能,我可能应该迭代要发送的对象和另一个线程上的请求,但是如何在主线程上调用委托函数?谢谢你的链接,我会调查的。
    猜你喜欢
    • 2020-09-10
    • 2012-02-17
    • 2020-08-27
    • 2011-11-04
    • 1970-01-01
    • 1970-01-01
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多