Quản lý nhiều kết nối NSURLConnection không đồng bộ


88

Tôi có rất nhiều mã lặp lại trong lớp của mình trông giống như sau:

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

Vấn đề với các yêu cầu không đồng bộ là khi bạn có nhiều yêu cầu khác nhau được thực hiện và bạn được chỉ định một người được ủy quyền để coi tất cả chúng như một thực thể, rất nhiều mã phân nhánh và xấu xí bắt đầu hình thành:

Chúng tôi đang lấy lại loại dữ liệu nào? Nếu nó chứa cái này, hãy làm cái kia, cái khác làm cái khác. Tôi nghĩ sẽ rất hữu ích nếu có thể gắn thẻ các yêu cầu không đồng bộ này, giống như bạn có thể gắn thẻ các lượt xem bằng ID.

Tôi rất tò mò về chiến lược nào là hiệu quả nhất để quản lý một lớp xử lý nhiều yêu cầu không đồng bộ.

Câu trả lời:


77

Tôi theo dõi các phản hồi trong CFMutableDictionaryRef được khóa bởi NSURLConnection được liên kết với nó. I E:

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

Có vẻ kỳ lạ khi sử dụng điều này thay vì NSMutableDictionary nhưng tôi làm điều đó vì CFDictionary này chỉ giữ lại các khóa của nó (NSURLConnection) trong khi NSDictionary sao chép các khóa của nó (và NSURLConnection không hỗ trợ sao chép).

Sau khi hoàn tất:

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

và bây giờ tôi có từ điển dữ liệu "thông tin" cho mỗi kết nối mà tôi có thể sử dụng để theo dõi thông tin về kết nối và từ điển "thông tin" đã chứa một đối tượng dữ liệu có thể thay đổi mà tôi có thể sử dụng để lưu trữ dữ liệu trả lời khi nó đi vào.

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

Vì có thể hai hoặc nhiều kết nối không đồng bộ có thể nhập các phương thức ủy nhiệm cùng một lúc, có điều gì cụ thể mà người ta cần làm để đảm bảo hành vi chính xác không?
PlagueHammer

(Tôi có tạo ra một câu hỏi mới đây yêu cầu này: stackoverflow.com/questions/1192294/... )
PlagueHammer

3
Điều này không an toàn cho luồng nếu người đại diện đang được gọi từ nhiều luồng. Bạn phải sử dụng các khóa loại trừ lẫn nhau để bảo vệ cấu trúc dữ liệu. Một giải pháp tốt hơn là phân lớp NSURLConnection và thêm các tham chiếu phản hồi và dữ liệu làm biến cá thể. Tôi đang cung cấp câu trả lời chi tiết hơn giải thích điều này tại câu hỏi của Nocturne: stackoverflow.com/questions/1192294/…
James Wald

4
Aldi ... nó luồng an toàn miễn là bạn bắt đầu tất cả các kết nối từ cùng một luồng (bạn có thể thực hiện dễ dàng bằng cách gọi phương thức kết nối bắt đầu của mình bằng cách sử dụng PerformSelector: onThread: withObject: waitUntilDone :). Đặt tất cả các kết nối trong NSOperationQueue có các vấn đề khác nhau nếu bạn cố gắng bắt đầu nhiều kết nối hơn các hoạt động đồng thời tối đa của hàng đợi (các hoạt động được xếp hàng đợi thay vì chạy đồng thời). NSOperationQueue hoạt động tốt cho các hoạt động liên kết CPU nhưng đối với các hoạt động liên kết mạng, bạn nên sử dụng phương pháp tiếp cận không sử dụng nhóm luồng có kích thước cố định.
Matt Gallagher

1
Chỉ muốn chia sẻ rằng đối với iOS 6.0 trở lên, bạn có thể sử dụng [NSMapTable weakToStrongObjectsMapTable]thay vì a CFMutableDictionaryRefvà đỡ rắc rối. Làm việc tốt cho tôi.
Shay Aviv

19

Tôi có một dự án mà tôi có hai NSURLConnections riêng biệt và muốn sử dụng cùng một đại biểu. Những gì tôi đã làm là tạo hai thuộc tính trong lớp của mình, một thuộc tính cho mỗi kết nối. Sau đó, trong phương thức ủy quyền, tôi kiểm tra xem kết nối đó là


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

Điều này cũng cho phép tôi hủy một kết nối cụ thể theo tên khi cần thiết.


hãy cẩn thận đây là vấn đề vì nó sẽ có các điều kiện đua
adit

Làm cách nào để bạn gán tên (saveConnection và sharingReturnedData) cho mỗi kết nối ngay từ đầu?
jsherk

@adit, không, không có điều kiện chủng tộc vốn có cho mã này. Bạn sẽ phải đi khá xa với mã tạo kết nối để tạo điều kiện cho cuộc đua
Mike Abdullah

'giải pháp' của bạn là chính xác những gì câu hỏi ban đầu đang tìm cách tránh, trích dẫn từ trên: '... rất nhiều mã phân nhánh và xấu xí bắt đầu hình thành ...'
stefanB

1
@adit Tại sao điều này sẽ dẫn đến tình trạng chủng tộc? Đó là một khái niệm mới đối với tôi.
guptron

16

Phân lớp NSURLConnection để giữ dữ liệu sạch sẽ, ít mã hơn một số câu trả lời khác, linh hoạt hơn và cần ít suy nghĩ hơn về quản lý tham chiếu.

// 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

Sử dụng nó như bạn muốn NSURLConnection và tích lũy dữ liệu trong thuộc tính dữ liệu của nó:

- (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];
}

Đó là nó.

Nếu bạn muốn đi xa hơn, bạn có thể thêm một khối để phục vụ như một cuộc gọi lại chỉ với một vài dòng mã nữa:

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

Đặt nó như thế này:

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

và gọi nó khi tải xong như thế này:

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

Bạn có thể mở rộng khối để chấp nhận các tham số hoặc chỉ cần truyền DataURLConnection làm đối số cho phương thức cần nó trong khối no-args như được hiển thị


Đây là một câu trả lời tuyệt vời rất hiệu quả cho trường hợp của tôi. Rất đơn giản và sạch sẽ!
jwarrent

8

ĐÂY KHÔNG PHẢI LÀ CÂU TRẢ LỜI MỚI. HÃY ĐỂ TÔI CHO BẠN BIẾT TÔI ĐÃ LÀM THẾ NÀO

Để phân biệt các NSURLConnection khác nhau trong các phương thức ủy nhiệm của cùng một lớp, tôi sử dụng NSMutableDictionary, để đặt và loại bỏ NSURLConnection, sử dụng nó (NSString *)descriptionlàm khóa.

Đối tượng mà tôi đã chọn setObject:forKeylà URL duy nhất được sử dụng để khởi tạo NSURLRequest, NSURLConnectionsử dụng.

Sau khi đặt NSURLConnection được đánh giá tại

-(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

Một cách tiếp cận mà tôi đã thực hiện là không sử dụng cùng một đối tượng làm đại biểu cho mỗi kết nối. Thay vào đó, tôi tạo một phiên bản mới của lớp phân tích cú pháp của tôi cho mỗi kết nối được kích hoạt và đặt ủy quyền cho phiên bản đó.


Đóng gói tốt hơn nhiều đối với một kết nối.
Kedar Paranjape

4

Hãy thử lớp tùy chỉnh của tôi, MultipleDownload , sẽ xử lý tất cả những điều này cho bạn.


trên iOS6 không thể sử dụng NSURLConnection làm khóa.
user501836,

2

Tôi thường tạo một loạt các từ điển. Mỗi từ điển có một chút thông tin nhận dạng, một đối tượng NSMutableData để lưu trữ phản hồi và bản thân kết nối. Khi phương thức đại biểu kết nối kích hoạt, tôi tra cứu từ điển của kết nối và xử lý nó cho phù hợp.


Ben, có thể hỏi bạn một đoạn mã mẫu được không? Tôi đang cố gắng hình dung cách bạn đang làm, nhưng tất cả không phải ở đó.
Coocoo4Cocoa

Riêng Ben, bạn tra từ điển như thế nào? Bạn không thể có một kho từ điển vì NSURLConnection không triển khai NSCopying (vì vậy nó không thể được sử dụng làm khóa).
Adam Ernst

Matt có một giải pháp tuyệt vời bên dưới bằng cách sử dụng CFMutableDictionary, nhưng tôi sử dụng một loạt các từ điển. Tra cứu yêu cầu lặp lại. Nó không phải là hiệu quả nhất, nhưng nó đủ nhanh.
Ben Gottlieb

2

Một tùy chọn chỉ là tự phân lớp NSURLConnection và thêm -tag hoặc phương thức tương tự. Thiết kế của NSURLConnection có chủ đích là rất trần trụi nên điều này hoàn toàn có thể chấp nhận được.

Hoặc có lẽ bạn có thể tạo một lớp MyURLConnectionController chịu trách nhiệm tạo và thu thập dữ liệu của kết nối. Sau đó, nó sẽ chỉ phải thông báo đối tượng bộ điều khiển chính của bạn sau khi tải xong.


2

trong iOS5 trở lên, bạn chỉ có thể sử dụng phương thức lớp sendAsynchronousRequest:queue:completionHandler:

Không cần theo dõi các kết nối vì phản hồi trả về trong trình xử lý hoàn thành.


1

Tôi thích ASIHTTPRequest .


Tôi thực sự thích việc triển khai 'khối' trong ASIHTTPRequest - nó giống như Các kiểu bên trong ẩn danh trong Java. Điều này đánh bại tất cả các giải pháp khác về độ sạch và tổ chức của mã.
Matt Lyons

1

Như đã chỉ ra trong các câu trả lời khác, bạn nên lưu trữ connectInfo ở đâu đó và tra cứu chúng theo kết nối.

Kiểu dữ liệu tự nhiên nhất cho điều này là NSMutableDictionary, nhưng nó không thể chấp nhậnNSURLConnection làm khóa vì các kết nối không thể sao chép.

Một tùy chọn khác để sử dụng NSURLConnectionslàm khóa trong NSMutableDictionarylà sử dụng NSValue valueWithNonretainedObject]:

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

0

Tôi quyết định phân lớp NSURLConnection và thêm thẻ, đại biểu và NSMutabaleData. Tôi có một lớp DataController xử lý tất cả việc quản lý dữ liệu, bao gồm cả các yêu cầu. Tôi đã tạo một giao thức DataControllerDelegate để các chế độ xem / đối tượng riêng lẻ có thể lắng nghe DataController để tìm hiểu khi nào yêu cầu của chúng được hoàn thành và nếu cần thì đã tải xuống bao nhiêu hoặc lỗi. Lớp DataController có thể sử dụng lớp con NSURLConnection để bắt đầu một yêu cầu mới và lưu đại biểu muốn lắng nghe DataController để biết khi nào yêu cầu kết thúc. Đây là giải pháp làm việc của tôi trong XCode 4.5.2 và ios 6.

Tệp DataController.h khai báo giao thức DataControllerDelegate). DataController cũng là một singleton:

@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

Các phương thức chính trong tệp 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]];
}

Và để bắt đầu một yêu cầu: [[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

Và 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

Mỗi NSURLConnection đều có thuộc tính băm, bạn có thể phân biệt tất cả theo thuộc tính này.

Ví dụ: tôi cần ghi nhớ một số thông tin nhất định trước và sau khi kết nối, vì vậy RequestManager của tôi có NSMutableDictionary để thực hiện việc này.

Một ví dụ:

// 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];

Sau khi yêu cầu:

- (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];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.