【问题标题】:Managing multiple asynchronous NSURLConnection connections管理多个异步 NSURLConnection 连接
【发布时间】:2010-09-24 20:10:15
【问题描述】:

我的课堂上有大量重复代码,如下所示:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                              delegate:self];

异步请求的问题是,当您有各种请求发出时,并且您有一个委托将它们全部视为一个实体,大量分支和丑陋的代码开始制定:

我们要返回什么样的数据?如果它包含这个,那就做那个,否则做其他。我认为能够标记这些异步请求会很有用,就像您能够使用 ID 标记视图一样。

我很好奇什么策略对于管理处理多个异步请求的类最有效。

【问题讨论】:

    标签: objective-c iphone cocoa cocoa-touch nsurlconnection


    【解决方案1】:

    我在 CFMutableDictionaryRef 中跟踪响应,该 CFMutableDictionaryRef 由与其关联的 NSURLConnection 键入。即:

    connectionToInfoMapping =
        CFDictionaryCreateMutable(
            kCFAllocatorDefault,
            0,
            &kCFTypeDictionaryKeyCallBacks,
            &kCFTypeDictionaryValueCallBacks);
    

    使用它而不是 NSMutableDictionary 可能看起来很奇怪,但我这样做是因为这个 CFDictionary 只保留它的键(NSURLConnection),而 NSDictionary 复制它的键(而 NSURLConnection 不支持复制)。

    完成后:

    CFDictionaryAddValue(
        connectionToInfoMapping,
        connection,
        [NSMutableDictionary
            dictionaryWithObject:[NSMutableData data]
            forKey:@"receivedData"]);
    

    现在我有一个用于每个连接的“信息”数据字典,我可以使用它来跟踪有关连接的信息,并且“信息”字典已经包含一个可变数据对象,我可以使用它来存储回复数据进来。

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        NSMutableDictionary *connectionInfo =
            CFDictionaryGetValue(connectionToInfoMapping, connection);
        [[connectionInfo objectForKey:@"receivedData"] appendData:data];
    }
    

    【讨论】:

    • 由于有可能同时有两个或多个异步连接进入委托方法,是否需要采取任何具体措施来确保正确的行为?
    • (我在这里创建了一个新问题:stackoverflow.com/questions/1192294/…
    • 如果从多个线程调用委托,这不是线程安全的。您必须使用互斥锁来保护数据结构。更好的解决方案是继承 NSURLConnection 并将响应和数据引用添加为实例变量。我在 Nocturne 的问题中提供了更详细的答案来解释这一点:stackoverflow.com/questions/1192294/…
    • Aldi...它线程安全的,前提是您从同一个线程启动所有连接(您可以通过使用 performSelector:onThread:withObject 调用启动连接方法轻松完成:等待直到完成:)。如果您尝试启动比队列的最大并发操作更多的连接(操作被排队而不是同时运行),则将所有连接放入 NSOperationQueue 会遇到不同的问题。 NSOperationQueue 适用于 CPU 绑定操作,但对于网络绑定操作,您最好使用不使用固定大小线程池的方法。
    • 只是想分享 iOS 6.0 及更高版本的内容,您可以使用 [NSMapTable weakToStrongObjectsMapTable] 而不是 CFMutableDictionaryRef 并省去麻烦。对我来说效果很好。
    【解决方案2】:

    我有一个项目,我有两个不同的 NSURLConnections,并且想使用同一个委托。我所做的是在我的类中创建两个属性,每个连接一个。然后在委托方法中,我检查一下是不是哪个连接

    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        if (connection == self.savingConnection) {
            [self.savingReturnedData appendData:data];
        }
        else {
            [self.sharingReturnedData appendData:data];
        }
    }
    

    这还允许我在需要时按名称取消特定连接。

    【讨论】:

    • 小心这是有问题的,因为它会有竞争条件
    • 首先如何为每个连接分配名称(savingConnection 和sharingReturnedData)?
    • @adit,不,此代码没有固有的竞争条件。您必须竭尽全力使用连接创建代码来创建竞争条件
    • 您的“解决方案”正是最初的问题想要避免的,从上面引用:“......许多分支和丑陋的代码开始形成......”
    • @adit 为什么这会导致竞争条件?这对我来说是一个新概念。
    【解决方案3】:

    子类化 NSURLConnection 来保存数据是干净的,比其他一些答案更少的代码,更灵活,并且需要更少的参考管理。

    // DataURLConnection.h
    #import <Foundation/Foundation.h>
    @interface DataURLConnection : NSURLConnection
    @property(nonatomic, strong) NSMutableData *data;
    @end
    
    // DataURLConnection.m
    #import "DataURLConnection.h"
    @implementation DataURLConnection
    @synthesize data;
    @end
    

    像使用 NSURLConnection 一样使用它并在其 data 属性中累积数据:

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        ((DataURLConnection *)connection).data = [[NSMutableData alloc] init];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [((DataURLConnection *)connection).data appendData:data];
    }
    

    就是这样。

    如果你想更进一步,你可以添加一个块作为回调,只需要多几行代码:

    // Add to DataURLConnection.h/.m
    @property(nonatomic, copy) void (^onComplete)();
    

    这样设置:

    DataURLConnection *con = [[DataURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    con.onComplete = ^{
        [self myMethod:con];
    };
    [con start];
    

    并在加载完成时调用它,如下所示:

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        ((DataURLConnection *)connection).onComplete();
    }
    

    您可以扩展块以接受参数,或者只是将 DataURLConnection 作为参数传递给在无参数块中需要它的方法,如图所示

    【讨论】:

    • 这是一个很棒的答案,非常适合我的情况。非常简单干净!
    【解决方案4】:

    这不是一个新的答案。请让我告诉你我是怎么做的

    为了区分同一类的委托方法中的不同 NSURLConnection,我使用 NSMutableDictionary 来设置和删除 NSURLConnection,使用它的 (NSString *)description 作为键。

    我为setObject:forKey 选择的对象是用于启动NSURLRequest 的唯一URL,NSURLConnection 使用。

    一旦设置 NSURLConnection 在

    -(void)connectionDidFinishLoading:(NSURLConnection *)connection, it can be removed from the dictionary.
    
    // This variable must be able to be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    NSMutableDictionary *connDictGET = [[NSMutableDictionary alloc] init];
    //...//
    
    // You can use any object that can be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    [connDictGET setObject:anyObjectThatCanBeReferencedFrom forKey:[aConnectionInstanceJustInitiated description]];
    //...//
    
    // At the delegate method, evaluate if the passed connection is the specific one which needs to be handled differently
    if ([[connDictGET objectForKey:[connection description]] isEqual:anyObjectThatCanBeReferencedFrom]) {
    // Do specific work for connection //
    
    }
    //...//
    
    // When the connection is no longer needed, use (NSString *)description as key to remove object
    [connDictGET removeObjectForKey:[connection description]];
    

    【讨论】:

      【解决方案5】:

      我采取的一种方法是不为每个连接使用与委托相同的对象。相反,我为每个触发的连接创建解析类的新实例,并将委托设置为该实例。

      【讨论】:

      • 一个连接的封装要好得多。
      【解决方案6】:

      试试我的自定义类MultipleDownload,它会为您处理所有这些。

      【讨论】:

      • 在iOS6上不能使用NSURLConnection作为key。
      【解决方案7】:

      我通常会创建一个字典数组。每个字典都有一些标识信息、一个用于存储响应的 NSMutableData 对象以及连接本身。当连接委托方法触发时,我会查找连接的字典并相应地处理它。

      【讨论】:

      • Ben,请问您要一段示例代码可以吗?我正在尝试设想您是如何做到的,但这还不是全部。
      • 特别是本,你是怎么查字典的?你不能有字典,因为 NSURLConnection 没有实现 NSCopying(所以它不能用作键)。
      • Matt 在下面使用 CFMutableDictionary 有一个很好的解决方案,但我使用了一个字典数组。查找需要迭代。它不是最有效的,但足够快。
      【解决方案8】:

      一种选择是自己继承 NSURLConnection 并添加 -tag 或类似方法。 NSURLConnection 的设计是故意非常简单的,所以这是完全可以接受的。

      或者您可以创建一个 MyURLConnectionController 类来负责创建和收集连接的数据。然后它只需要在加载完成后通知您的主控制器对象。

      【讨论】:

        【解决方案9】:

        在iOS5及以上你可以只使用类方法 sendAsynchronousRequest:queue:completionHandler:

        由于响应在完成处理程序中返回,因此无需跟踪连接。

        【讨论】:

          【解决方案10】:

          我喜欢ASIHTTPRequest

          【讨论】:

          • 我真的很喜欢 ASIHTTPRequest 中的“块”实现——它就像 Java 中的匿名内部类型。这在代码清洁度和组织方面优于所有其他解决方案。
          【解决方案11】:

          正如其他答案所指出的,您应该将 connectionInfo 存储在某处并通过连接查找它们。

          最自然的数据类型是NSMutableDictionary,但它不能接受NSURLConnection 作为键,因为连接是不可复制的。

          NSMutableDictionary 中使用NSURLConnections 作为键的另一种选择是使用NSValue valueWithNonretainedObject]

          NSMutableDictionary* dict = [NSMutableDictionary dictionary];
          NSValue *key = [NSValue valueWithNonretainedObject:aConnection]
          /* store: */
          [dict setObject:connInfo forKey:key];
          /* lookup: */
          [dict objectForKey:key];
          

          【讨论】:

            【解决方案12】:

            我决定继承 NSURLConnection 并添加标签、委托和 NSMutabaleData。我有一个处理所有数据管理的 DataController 类,包括请求。我创建了一个 DataControllerDelegate 协议,以便各个视图/对象可以监听 DataController 以了解他们的请求何时完成,以及如果需要已经下载了多少或错误。 DataController 类可以使用 NSURLConnection 子类开始一个新的请求,并保存想要监听 DataController 以了解请求何时完成的委托。这是我在 XCode 4.5.2 和 ios 6 中的工作解决方案。

            声明 DataControllerDelegate 协议的 DataController.h 文件)。 DataController 也是一个单例:

            @interface DataController : NSObject
            
            @property (strong, nonatomic)NSManagedObjectContext *context;
            @property (strong, nonatomic)NSString *accessToken;
            
            +(DataController *)sharedDataController;
            
            -(void)generateAccessTokenWith:(NSString *)email password:(NSString *)password delegate:(id)delegate;
            
            @end
            
            @protocol DataControllerDelegate <NSObject>
            
            -(void)dataFailedtoLoadWithMessage:(NSString *)message;
            -(void)dataFinishedLoading;
            
            @end
            

            DataController.m文件中的关键方法:

            -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
                NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
                NSLog(@"DidReceiveResponse from %@", customConnection.tag);
                [[customConnection receivedData] setLength:0];
            }
            
            -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
                NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
                NSLog(@"DidReceiveData from %@", customConnection.tag);
                [customConnection.receivedData appendData:data];
            
            }
            
            -(void)connectionDidFinishLoading:(NSURLConnection *)connection {
                NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
                NSLog(@"connectionDidFinishLoading from %@", customConnection.tag);
                NSLog(@"Data: %@", customConnection.receivedData);
                [customConnection.dataDelegate dataFinishedLoading];
            }
            
            -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
                NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
                NSLog(@"DidFailWithError with %@", customConnection.tag);
                NSLog(@"Error: %@", [error localizedDescription]);
                [customConnection.dataDelegate dataFailedtoLoadWithMessage:[error localizedDescription]];
            }
            

            发起请求:[[NSURLConnectionWithDelegate alloc] initWithRequest:request delegate:self startImmediately:YES tag:@"Login" dataDelegate:delegate];

            NSURLConnectionWithDelegate.h: @protocol DataControllerDelegate;

            @interface NSURLConnectionWithDelegate : NSURLConnection
            
            @property (strong, nonatomic) NSString *tag;
            @property id <DataControllerDelegate> dataDelegate;
            @property (strong, nonatomic) NSMutableData *receivedData;
            
            -(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate;
            
            @end
            

            还有 NSURLConnectionWithDelegate.m:

            #import "NSURLConnectionWithDelegate.h"
            
            @implementation NSURLConnectionWithDelegate
            
            -(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate {
                self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
                if (self) {
                    self.tag = tag;
                    self.dataDelegate = dataDelegate;
                    self.receivedData = [[NSMutableData alloc] init];
                }
                return self;
            }
            
            @end
            

            【讨论】:

              【解决方案13】:

              每个 NSURLConnection 都有一个 hash 属性,你可以通过这个属性来区分所有的。

              例如,我需要在连接之前和之后维护某些信息,所以我的 RequestManager 有一个 NSMutableDictionary 来执行此操作。

              一个例子:

              // Make Request
              NSURLRequest *request = [NSURLRequest requestWithURL:url];
              NSURLConnection *c = [[NSURLConnection alloc] initWithRequest:request delegate:self];
              
              // Append Stuffs 
              NSMutableDictionary *myStuff = [[NSMutableDictionary alloc] init];
              [myStuff setObject:@"obj" forKey:@"key"];
              NSNumber *connectionKey = [NSNumber numberWithInt:c.hash];
              
              [connectionDatas setObject:myStuff forKey:connectionKey];
              
              [c start];
              

              请求后:

              - (void)connectionDidFinishLoading:(NSURLConnection *)connection
              {
                  NSLog(@"Received %d bytes of data",[responseData length]);
              
                  NSNumber *connectionKey = [NSNumber numberWithInt:connection.hash];
              
                  NSMutableDictionary *myStuff = [[connectionDatas objectForKey:connectionKey]mutableCopy];
                  [connectionDatas removeObjectForKey:connectionKey];
              }
              

              【讨论】:

                猜你喜欢
                • 2012-11-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-12-20
                • 1970-01-01
                • 2016-05-25
                • 2011-11-06
                • 1970-01-01
                相关资源
                最近更新 更多