여러 비동기 NSURLConnection 연결 관리


88

내 수업에는 다음과 같은 반복되는 코드가 많이 있습니다.

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

비동기 요청의 문제는 다양한 요청이 발생하고 모든 요청을 하나의 엔터티로 처리하도록 할당 된 대리자가있을 때 많은 분기 및 추악한 코드가 공식화되기 시작한다는 것입니다.

우리는 어떤 종류의 데이터를 받고 있습니까? 이것이 포함되어 있으면 그렇게하고 그렇지 않으면 다른 작업을 수행하십시오. ID로 뷰에 태그를 지정할 수있는 것처럼 이러한 비동기 요청에 태그를 지정할 수 있으면 유용 할 것입니다.

여러 비동기 요청을 처리하는 클래스를 관리하는 데 가장 효율적인 전략이 무엇인지 궁금했습니다.

답변:


77

나는 그것과 관련된 NSURLConnection에 의해 입력 된 CFMutableDictionaryRef에서 응답을 추적한다. 즉 :

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는 ... 그것 입니다 onThread : withObject : waitUntilDone : 스레드 안전 당신이 performSelector를 사용하여 시작 연결 방법을 호출하여 쉽게 할 수있는 동일한 스레드 (모든 연결을 시작 제공. 모든 연결을 NSOperationQueue에 넣으면 대기열의 최대 동시 작업 수보다 더 많은 연결을 시작하려고하면 다른 문제가 발생합니다 (작업이 동시에 실행되는 대신 대기열에 추가됨). NSOperationQueue는 CPU 바운드 작업에 잘 작동하지만 네트워크 바운드 작업의 경우 고정 크기 스레드 풀을 사용하지 않는 접근 방식을 사용하는 것이 좋습니다.
Matt Gallagher

1
iOS 6.0 이상에서 공유하고 싶을 때 [NSMapTable weakToStrongObjectsMapTable]대신 a CFMutableDictionaryRef를 사용하여 번거 로움을 덜 수 있습니다. 나를 위해 잘 작동했습니다.
셰이 아비브

19

두 개의 서로 다른 NSURLConnection이 있고 동일한 대리자를 사용하려는 프로젝트가 있습니다. 내가 한 것은 각 연결에 대해 하나씩 클래스에 두 개의 속성을 만드는 것입니다. 그런 다음 위임 메서드에서 어떤 연결인지 확인합니다.


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

또한 필요할 때 이름으로 특정 연결을 취소 할 수 있습니다.


경쟁 조건이 있기 때문에 문제가 될 수 있습니다.
adit

처음에 각 연결의 이름 (savingConnection 및 sharingReturnedData)을 어떻게 할당합니까?
jsherk

@adit, 아니요,이 코드에는 고유 한 경쟁 조건이 없습니다. 경쟁 조건을 생성하려면 연결 생성 코드를 사용하여 꽤 멀리 가야합니다
Mike Abdullah

당신의 '솔루션'원래의 질문은 위에서 인용 피하기 위해 찾고 정확히 '... 분기 못생긴 많은 코드가 공식화하기 시작 ...'
stefanB

1
@adit 왜 이것이 경쟁 조건으로 이어질까요? 저에게는 새로운 개념입니다.
guptron 2013-07-05

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처럼 사용하고 데이터 속성에 데이터를 누적합니다.

- (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을 인수로 전달할 수 있습니다.


이것은 제 경우에 정말 잘 작동 한 환상적인 답변입니다. 매우 간단하고 깨끗합니다!
jwarrent

8

이것은 새로운 답이 아닙니다. 내가 어떻게했는지 보여 줄게

동일한 클래스의 대리자 메서드 내에서 다른 NSURLConnection을 구별하기 위해 NSMutableDictionary를 사용하여 해당 (NSString *)descriptionas 키를 사용하여 NSURLConnection을 설정하고 제거합니다 .

내가 위해 선택한 객체는 setObject:forKey시작에 사용되는 고유 한 URL 인 NSURLRequestNSURLConnection용도.

일단 설정된 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 객체 및 연결 자체가 있습니다. 연결 대리자 메서드가 실행되면 연결 사전을 찾아 그에 따라 처리합니다.


벤, 샘플 코드를 요청해도 될까요? 나는 당신이 그것을 어떻게하고 있는지 상상하려고 노력하고 있지만 그것이 전부는 아닙니다.
Coocoo4Cocoa

특히 Ben은 사전을 어떻게 검색합니까? NSURLConnection이 NSCopying을 구현하지 않기 때문에 사전 사전을 가질 수 없습니다 (따라서 키로 사용할 수 없음).
아담 에른스트

Matt는 CFMutableDictionary를 사용하여 아래에 훌륭한 솔루션을 제공하지만 사전 배열을 사용합니다. 조회에는 반복이 필요합니다. 가장 효율적은 아니지만 충분히 빠릅니다.
Ben Gottlieb

2

한 가지 옵션은 NSURLConnection을 직접 하위 클래스로 만들고 -tag 또는 유사한 메소드를 추가하는 것입니다. NSURLConnection의 디자인은 의도적으로 매우 베어 뼈이므로 완벽하게 허용됩니다.

또는 연결 데이터를 만들고 수집하는 MyURLConnectionController 클래스를 만들 수도 있습니다. 그런 다음로드가 완료된 후에 만 ​​주 컨트롤러 개체에 알리면됩니다.


2

iOS5 이상에서는 클래스 메서드를 사용할 수 있습니다. 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에는 해시 속성이 있으므로이 속성으로 모두 구별 할 수 있습니다.

예를 들어 연결 전후에 특정 정보를 관리해야하므로 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];
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.