การจัดการการเชื่อมต่อ NSURLC แบบอะซิงโครนัสหลายรายการ


88

ฉันมีรหัสซ้ำจำนวนมากในชั้นเรียนของฉันซึ่งมีลักษณะดังนี้:

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

ปัญหาเกี่ยวกับคำขอแบบอะซิงโครนัสคือเมื่อคุณมีคำขอต่าง ๆ เกิดขึ้นและคุณมีผู้รับมอบสิทธิ์ที่ได้รับมอบหมายให้ปฏิบัติต่อพวกเขาทั้งหมดเป็นเอนทิตีเดียวโค้ดที่แตกแขนงและน่าเกลียดจำนวนมากเริ่มที่จะกำหนดให้ดำเนินการต่อไป:

ข้อมูลประเภทใดที่เราได้รับกลับมา? หากมีสิ่งนี้ให้ทำอย่างอื่นทำอย่างอื่น ฉันคิดว่าจะมีประโยชน์ในการติดแท็กคำขอแบบอะซิงโครนัสเหล่านี้เช่นเดียวกับที่คุณสามารถแท็กมุมมองด้วย ID ได้

ฉันอยากรู้ว่ากลยุทธ์ใดที่มีประสิทธิภาพมากที่สุดสำหรับการจัดการคลาสที่จัดการกับคำขอแบบอะซิงโครนัสหลายรายการ

คำตอบ:


77

ฉันติดตามการตอบสนองใน CFMutableDictionaryRef ที่คีย์โดย NSURLConnection ที่เกี่ยวข้อง กล่าวคือ:

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

อาจดูแปลกที่จะใช้สิ่งนี้แทน NSMutableDictionary แต่ฉันทำเพราะ CFDictionary นี้เก็บคีย์ไว้เท่านั้น (การเชื่อมต่อ NSURLC) ในขณะที่ 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];
}

เนื่องจากมีความเป็นไปได้ที่การเชื่อมต่อแบบอะซิงโครนัสตั้งแต่สองรายการขึ้นไปอาจเข้าสู่วิธีการมอบสิทธิ์ในแต่ละครั้งมีสิ่งใดที่ต้องทำเพื่อให้แน่ใจว่ามีพฤติกรรมที่ถูกต้องหรือไม่?
Debajit

(ฉันสร้างคำถามใหม่ที่นี่เพื่อถามสิ่งนี้stackoverflow.com/questions/1192294/… )
Debajit

3
สิ่งนี้ไม่ปลอดภัยต่อเธรดหากมีการเรียกตัวแทนจากหลายเธรด คุณต้องใช้การล็อกการยกเว้นร่วมกันเพื่อปกป้องโครงสร้างข้อมูล ทางออกที่ดีกว่าคือการเชื่อมต่อ NSURLC ย่อยและเพิ่มการตอบสนองและการอ้างอิงข้อมูลเป็นตัวแปรอินสแตนซ์ ฉันกำลังให้คำตอบโดยละเอียดมากขึ้นเพื่ออธิบายสิ่งนี้ในคำถามของ Nocturne: stackoverflow.com/questions/1192294/…
James Wald

4
Aldi ... เป็นเธรดที่ปลอดภัยหากคุณเริ่มการเชื่อมต่อทั้งหมดจากเธรดเดียวกัน (ซึ่งคุณสามารถทำได้อย่างง่ายดายโดยเรียกใช้วิธีการเริ่มต้นการเชื่อมต่อของคุณโดยใช้ performSelector: onThread: withObject: waitUntilDone :) การใส่การเชื่อมต่อทั้งหมดใน NSOperationQueue มีปัญหาที่แตกต่างกันหากคุณพยายามเริ่มการเชื่อมต่อมากกว่าการดำเนินการพร้อมกันสูงสุดของคิว (การดำเนินการจะถูกจัดคิวแทนการรันพร้อมกัน) NSOperationQueue ทำงานได้ดีสำหรับการดำเนินการที่เชื่อมต่อกับ CPU แต่สำหรับการดำเนินการที่เชื่อมโยงกับเครือข่ายคุณควรใช้วิธีการที่ไม่ใช้เธรดพูลขนาดคงที่
Matt Gallagher

1
แค่อยากจะแชร์สิ่งนั้นสำหรับ iOS 6.0 ขึ้นไปคุณสามารถใช้ a [NSMapTable weakToStrongObjectsMapTable]แทน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

คุณจะกำหนดชื่อ (savingConnection และ sharingReturnedData) สำหรับการเชื่อมต่อแต่ละครั้งได้อย่างไร?
jsherk

@adit ไม่มีเงื่อนไขการแข่งขันที่มีอยู่ในรหัสนี้ คุณต้องออกไปไกลพอสมควรด้วยรหัสการสร้างการเชื่อมต่อเพื่อสร้างเงื่อนไขการแข่งขัน
Mike Abdullah

'วิธีแก้ปัญหา' ของคุณคือสิ่งที่คำถามเดิมต้องการหลีกเลี่ยงโดยอ้างจากด้านบน: '... รหัสที่แตกแขนงและน่าเกลียดจำนวนมากเริ่มกำหนด ... '
stefanB

1
@adit ทำไมสิ่งนี้ถึงนำไปสู่สภาพการแข่งขัน? เป็นแนวคิดใหม่สำหรับฉัน
abc123

16

การเชื่อมต่อ NSURL คลาสย่อยเพื่อเก็บข้อมูลนั้นสะอาดโค้ดน้อยกว่าคำตอบอื่น ๆ บางคำมีความยืดหยุ่นมากกว่าและไม่ต้องใช้ความคิดเกี่ยวกับการจัดการข้อมูลอ้างอิง

// 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 เป็นอาร์กิวเมนต์ไปยังเมธอดที่ต้องการภายในบล็อก no-args ดังที่แสดง


นี่เป็นคำตอบที่ยอดเยี่ยมที่ใช้ได้ดีกับกรณีของฉัน เรียบง่ายและสะอาดมาก!
jwarrent

8

นี่ไม่ใช่คำตอบใหม่ โปรดให้ฉันแสดงให้คุณเห็นว่าฉันทำได้อย่างไร

ในการแยกแยะ NSURLConnection ที่แตกต่างกันภายในวิธีการมอบหมายของคลาสเดียวกันฉันใช้ NSMutableDictionary เพื่อตั้งค่าและลบ NSURLConnection โดยใช้(NSString *)descriptionเป็นคีย์

วัตถุที่ฉันเลือกsetObject:forKeyคือ URL เฉพาะที่ใช้สำหรับการเริ่มต้นNSURLRequestการ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

แนวทางหนึ่งที่ฉันใช้คืออย่าใช้วัตถุเดียวกันกับผู้รับมอบสิทธิ์สำหรับการเชื่อมต่อแต่ละครั้ง แต่ฉันสร้างอินสแตนซ์ใหม่ของคลาสการแยกวิเคราะห์ของฉันสำหรับการเชื่อมต่อแต่ละครั้งที่ถูกปิดและตั้งค่าผู้รับมอบสิทธิ์เป็นอินสแตนซ์นั้น


การห่อหุ้มที่ดีขึ้นมากเมื่อเทียบกับการเชื่อมต่อเดียว
Kedar Paranjape

4

ลองใช้คลาสที่กำหนดเองMultipleDownloadซึ่งจัดการสิ่งเหล่านี้ให้คุณ


บน iOS6 ไม่สามารถใช้ NSURLConnection เป็นกุญแจสำคัญ
user501836

2

ฉันมักจะสร้างพจนานุกรมหลายชุด พจนานุกรมแต่ละตัวมีข้อมูลระบุเล็กน้อยอ็อบเจ็กต์ NSMutableData เพื่อจัดเก็บการตอบสนองและการเชื่อมต่อเอง เมื่อเมธอดผู้รับมอบสิทธิ์การเชื่อมต่อเริ่มทำงานฉันจะค้นหาพจนานุกรมของการเชื่อมต่อและจัดการตามนั้น


เบ็นจะขอโค้ดตัวอย่างจากคุณได้ไหม ฉันพยายามจินตนาการว่าคุณกำลังทำมันอย่างไร แต่มันไม่ได้อยู่ที่นั่นทั้งหมด
Coocoo4Cocoa

โดยเฉพาะเบ็นคุณจะค้นหาพจนานุกรมได้อย่างไร? คุณไม่สามารถมีพจนานุกรมของพจนานุกรมได้เนื่องจาก NSURLConnection ไม่ได้ใช้ NSCopying (จึงไม่สามารถใช้เป็นคีย์ได้)
Adam Ernst

Matt มีวิธีแก้ปัญหาที่ยอดเยี่ยมด้านล่างโดยใช้ CFMutableDictionary แต่ฉันใช้พจนานุกรมหลายชุด การค้นหาต้องมีการทำซ้ำ มันไม่ได้มีประสิทธิภาพสูงสุด แต่ก็เร็วพอ
Ben Gottlieb

2

ทางเลือกหนึ่งคือการเชื่อมต่อ NSURLC คลาสย่อยด้วยตัวคุณเองและเพิ่ม a -tag หรือวิธีการที่คล้ายกัน การออกแบบของ NSURLConnection มีความตั้งใจที่จะให้กระดูกเปลือยมากดังนั้นจึงเป็นที่ยอมรับได้อย่างสมบูรณ์แบบ

หรือบางทีคุณอาจสร้างคลาส MyURLConnectionController ที่รับผิดชอบในการสร้างและรวบรวมข้อมูลของการเชื่อมต่อ จากนั้นจะต้องแจ้งอ็อบเจ็กต์คอนโทรลเลอร์หลักของคุณเมื่อการโหลดเสร็จสิ้น


2

ใน iOS5 ขึ้นไปคุณสามารถใช้เมธอดคลาสได้ sendAsynchronousRequest:queue:completionHandler:

ไม่จำเป็นต้องติดตามการเชื่อมต่อเนื่องจากการตอบกลับจะกลับมาในตัวจัดการการเสร็จสิ้น


1

ฉันชอบASIHTTPRequest


ฉันชอบการใช้งาน 'บล็อก' ใน ASIHTTPRequest มาก - มันเหมือนกับ Anonymous Inner Types ใน 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

ฉันตัดสินใจที่จะ subclass NSURLConnection และเพิ่มแท็กตัวแทนและ NSMutabaleData ฉันมีคลาส DataController ที่จัดการการจัดการข้อมูลทั้งหมดรวมถึงคำขอ ฉันสร้างโปรโตคอล DataControllerDelegate เพื่อให้มุมมอง / วัตถุแต่ละรายการสามารถฟัง DataController เพื่อดูว่าเมื่อใดที่คำขอของพวกเขาเสร็จสิ้นและหากจำเป็นต้องดาวน์โหลดหรือมีข้อผิดพลาดมากแค่ไหน คลาส DataController สามารถใช้คลาสย่อย NSURLConnection เพื่อเริ่มการร้องขอใหม่และบันทึกผู้รับมอบสิทธิ์ที่ต้องการฟัง DataController เพื่อให้ทราบเมื่อการร้องขอเสร็จสิ้น นี่คือโซลูชันการทำงานของฉันใน XCode 4.5.2 และ ios 6

ไฟล์ DataController.h ที่ประกาศโปรโตคอล DataControllerDelegate) 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

การเชื่อมต่อ NSURLC ทุกตัวมีแอตทริบิวต์แฮชคุณสามารถแยกแยะทั้งหมดได้ด้วยคุณสมบัตินี้

ตัวอย่างเช่นฉันต้องเก็บข้อมูลบางอย่างก่อนและหลังการเชื่อมต่อดังนั้น 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.