管理多个异步NSURLConnection连接


88

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

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

异步请求的问题是,当您关闭各种请求,并且分配了一个委托将它们全部视为一个实体时,许多分支和丑陋的代码开始制定规则:

我们要获得什​​么样的数据?如果包含此内容,请执行此操作,否则执行其他操作。我认为能够标记这些异步请求将很有用,就像您可以使用ID标记视图一样。

我很好奇哪种策略对管理可处理多个异步请求的类最有效。

Answers:


77

我在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];
}

由于可能同时有两个或多个异步连接可以进入委托方法,因此,为了确保正确的行为,是否需要做一些具体的事情?
PlagueHammer

(我在这里创建了一个新问题,问这个问题:stackoverflow.com/questions/1192294/…
PlagueHammer

3
如果从多个线程调用委托,则这不是线程安全的。您必须使用互斥锁来保护数据结构。更好的解决方案是将NSURLConnection子类化,并将响应和数据引用添加为实例变量。我提供一个更详细的回答在夜曲的问题解释如下:stackoverflow.com/questions/1192294/...
詹姆斯沃尔德

4
Aldi ...这线程安全的,只要您从同一线程启动所有连接即可(您可以通过使用performSelector:onThread:withObject:waitUntilDone:调用启动连接方法来轻松完成此操作)。如果您尝试启动的连接数超过队列的最大并发操作数(将操作排入队列而不是并发运行),则将所有连接放入NSOperationQueue会有不同的问题。NSOperationQueue对于受CPU限制的操作非常有效,但对于受网络限制的操作,最好不要使用固定大小的线程池。
马特·加拉格尔

1
只是想与iOS 6.0及更高版本共享,您可以使用a [NSMapTable weakToStrongObjectsMapTable]代替a CFMutableDictionaryRef并节省麻烦。对我来说很好。
Shay Aviv

19

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


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

这也使我可以在需要时按名称取消特定的连接。


小心,这是有问题的,因为它将具有比赛条件
adit 2012年

首先如何为每个连接分配名称(savingConnection和SharingReturnedData)?
jsherk 2012年

@adit,不,此代码没有固有的竞争条件。您必须在连接创建代码方面走得很远才能创建竞争条件
Mike Abdullah

您的“解决方案”正是原始问题想要避免的,从上面引述:“ ...大量分支和丑陋的代码开始形成……”
stefanB 2013年

1
@adit为什么会导致比赛状态?对我来说这是一个新概念。
guptron

16

子类化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作为参数传递给在no-args块中需要它的方法,如图所示


这是一个很好的答案,对我的案例非常有效。非常简单干净!
jwarrent

8

这不是一个新的答案。请让我告诉你我如何

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

我选择的对象setObject:forKey是用于启动NSURLRequestNSURLConnection使用)的唯一URL 。

设置后,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

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


关于一种连接的更好的封装。
Kedar Paranjape 2015年


2

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


Ben,可以请您提供一些示例代码吗?我正在尝试设想您的工作方式,但是还不止这些。
Coocoo4Cocoa

特别是Ben,您如何查找字典?由于NSURLConnection没有实现NSCopying(因此它不能用作键),因此您将没有字典的字典。
亚当·恩斯特,

Matt在下面使用CFMutableDictionary提供了一个出色的解决方案,但我使用了一系列字典。查找需要迭代。它不是最有效的,但是足够快。
本·戈特利布

2

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

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


2

在iOS5及更高版本中,您只能使用class方法 sendAsynchronousRequest:queue:completionHandler:

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


1

我喜欢ASIHTTPRequest


我真的很喜欢ASIHTTPRequest中的“块”实现-就像Java中的匿名内部类型一样。在代码清洁度和组织性方面,这击败了所有其他解决方案。
Matt Lyons

1

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

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

NSURLConnections用作键键入的另一种方法NSMutableDictionary是使用NSValue valueWithNonretainedObject]

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

0

我决定继承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

0

每个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];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.