Birden çok asenkron NSURLConnection bağlantısını yönetme


88

Sınıfımda aşağıdakine benzeyen bir ton yinelenen kod var:

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

Eşzamansız isteklerle ilgili sorun, çeşitli istekleriniz olduğunda ve bunların tümünü tek bir varlık olarak ele alacak bir temsilciniz olduğunda, çok sayıda dallanma ve çirkin kod şu şekilde formüle etmeye başlar:

Ne tür verileri geri alıyoruz? Eğer bunu içeriyorsa, bunu yap, yoksa diğerini yap. Sanki görünümleri kimliklerle etiketleyebiliyorsunuz gibi, bu eşzamansız istekleri etiketleyebilmenin faydalı olacağını düşünüyorum.

Eşzamansız birden çok isteği işleyen bir sınıfı yönetmek için hangi stratejinin en verimli olduğunu merak ediyordum.

Yanıtlar:


77

Yanıtları kendisiyle ilişkili NSURLConnection tarafından anahtarlanmış bir CFMutableDictionaryRef içinde izliyorum. yani:

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

Bunu NSMutableDictionary yerine kullanmak garip görünebilir, ancak bunu yapıyorum çünkü bu CFDictionary yalnızca anahtarlarını (NSURLConnection) korurken NSDictionary anahtarlarını kopyalar (ve NSURLConnection kopyalamayı desteklemez).

Bu bittiğinde:

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

ve şimdi, her bağlantı için, bağlantı hakkındaki bilgileri izlemek için kullanabileceğim bir "bilgi" veri sözlüğüm var ve "bilgi" sözlüğü, yanıt verisini geldikçe saklamak için kullanabileceğim, değiştirilebilir bir veri nesnesi içeriyor.

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

Bir seferde iki veya daha fazla eşzamansız bağlantının temsilci yöntemlerine girmesi mümkün olduğundan, doğru davranışı sağlamak için yapılması gereken belirli bir şey var mı?
Debajit

(Bunu soran burada yeni bir soru oluşturdum: stackoverflow.com/questions/1192294/… )
Debajit

3
Temsilci birden çok iş parçacığından çağrılıyorsa, bu iş parçacığı açısından güvenli değildir. Veri yapılarını korumak için karşılıklı dışlama kilitleri kullanmanız gerekir. Daha iyi bir çözüm, NSURLConnection'ı alt sınıflandırmak ve örnek değişkenleri olarak yanıt ve veri referanslarını eklemektir. Bunu Nocturne'un sorusuna açıklayan daha ayrıntılı bir cevap veriyorum: stackoverflow.com/questions/1192294/…
James Wald

4
Aldi ... Bu ise onThread: withObject: waitUntilDone :) parçacığı güvenli Eğer performSelector izleyerek başlangıç bağlantı yöntemini çağırarak kolayca yapabilirsiniz aynı parçacığı (tüm bağlantıları başlamak sağladı. Kuyruğun maksimum eşzamanlı işleminden daha fazla bağlantı başlatmaya çalışırsanız, tüm bağlantıları bir NSOperationQueue'ya yerleştirmenin farklı sorunları vardır (işlemler eşzamanlı olarak çalışmak yerine sıraya alınır). NSOperationQueue, CPU'ya bağlı işlemler için iyi çalışır, ancak ağa bağlı işlemler için, sabit boyutlu bir iş parçacığı havuzu kullanmayan bir yaklaşım kullanmanız daha iyidir.
Matt Gallagher

1
Bunu iOS 6.0 ve üstü için paylaşmak istedim, a [NSMapTable weakToStrongObjectsMapTable]yerine a kullanabilir CFMutableDictionaryRefve güçlükten kurtulabilirsiniz. Benim için iyi çalıştı.
Shay Aviv

19

İki ayrı NSURLC bağlantımın olduğu ve aynı delegeyi kullanmak istediğim bir projem var. Yaptığım şey, sınıfımda her bağlantı için bir tane olmak üzere iki özellik oluşturmaktı. Daha sonra delege yönteminde, hangi bağlantı olduğunu kontrol ediyorum.


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

Bu ayrıca gerektiğinde belirli bir bağlantıyı isme göre iptal etmeme izin veriyor.


dikkatli olun, yarış koşulları olacağından bu sorunludur
adit

İlk etapta her bir bağlantı için adları (kaydetme Bağlantı ve geri dönen Verileri paylaşma) nasıl atarsınız?
pislik

@adit, hayır, bu koda özgü bir yarış koşulu yoktur. Bir yarış koşulu oluşturmak için bağlantı oluşturma kodu ile oldukça uzaklara gitmeniz gerekir
Mike Abdullah

Yukarıdan alıntı yaparak, 'çözümünüz' tam olarak asıl sorunun kaçınmak istediği şeydir: '... çok sayıda dallanma ve çirkin kod formüle etmeye başlar ...'
stefanB

1
@adit Bu neden bir yarış durumuna yol açar? Bu benim için yeni bir kavram.
abc123

16

NSURLConnection'ın verileri tutmak için alt sınıflandırma temiz, diğer yanıtların bazılarından daha az kod, daha esnektir ve referans yönetimi hakkında daha az düşünmeyi gerektirir.

// 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 gibi kullanın ve verileri data özelliğinde toplayı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];
}

Bu kadar.

Daha ileri gitmek isterseniz, sadece birkaç satır daha kodla geri arama işlevi görecek bir blok ekleyebilirsiniz:

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

Bunu şu şekilde ayarlayın:

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

ve yükleme şu şekilde bittiğinde çağırın:

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

Bloğu, parametreleri kabul edecek şekilde genişletebilir veya yalnızca DataURLConnection'ı, gösterildiği gibi args içermeyen blok içinde ihtiyaç duyan yönteme bir argüman olarak iletebilirsiniz.


Bu, davam için gerçekten işe yarayan harika bir cevap. Çok basit ve temiz!
jwarrent

8

BU YENİ BİR CEVAP DEĞİLDİR. LÜTFEN NASIL YAPTIK

Aynı sınıfın temsilci yöntemlerinde farklı NSURLConnection'ı ayırt etmek için, NSURLConnection'ı (NSString *)descriptionanahtar olarak kullanarak ayarlamak ve kaldırmak için NSMutableDictionary kullanıyorum .

Seçtiğim nesne , kullanımları setObject:forKeybaşlatmak için kullanılan benzersiz URL'dir .NSURLRequestNSURLConnection

Bir kez ayarlandıktan sonra 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

Benim kullandığım bir yaklaşım, her bağlantı için temsilciyle aynı nesneyi kullanmamaktır. Bunun yerine, başlatılan her bağlantı için ayrıştırma sınıfımın yeni bir örneğini oluşturuyorum ve temsilciyi bu örneğe ayarlıyorum.


Bir bağlantıya göre çok daha iyi kapsülleme.
Kedar Paranjape


2

Genellikle bir dizi sözlük oluştururum. Her sözlüğün bir parça tanımlayıcı bilgisi, yanıtı depolamak için bir NSMutableData nesnesi ve bağlantının kendisi vardır. Bir bağlantı temsilcisi yöntemi tetiklendiğinde, bağlantının sözlüğüne bakar ve buna göre işlerim.


Ben, senden bir parça örnek kod isteyebilir miyim? Bunu nasıl yaptığınızı hayal etmeye çalışıyorum, ama hepsi orada değil.
Coocoo4Cocoa

Özellikle Ben, sözlüğe nasıl bakıyorsun? NSURLConnection NSCopying'i uygulamadığından (bu nedenle anahtar olarak kullanılamaz) bir sözlük sözlüğüne sahip olamazsınız.
Adam Ernst

Matt'in CFMutableDictionary kullanarak mükemmel bir çözümü var, ancak ben bir dizi sözlük kullanıyorum. Bir arama, bir yineleme gerektirir. En verimli değil, ancak yeterince hızlı.
Ben Gottlieb

2

Seçeneklerden biri, NSURLConnection'ı kendi kendinize alt sınıfa koymak ve -tag veya benzeri bir yöntem eklemektir. NSURLConnection tasarımı kasıtlı olarak çok çıplak kemiklerdir, bu nedenle bu tamamen kabul edilebilir.

Veya belki bir bağlantının verilerini oluşturmaktan ve toplamaktan sorumlu bir MyURLConnectionController sınıfı oluşturabilirsiniz. Bu durumda, yalnızca yükleme bittiğinde ana denetleyici nesnenizi bilgilendirmesi gerekir.


2

iOS5 ve üzeri sürümlerde yalnızca sınıf yöntemini kullanabilirsiniz sendAsynchronousRequest:queue:completionHandler:

Yanıt tamamlama işleyicisine geri döndüğü için bağlantıları takip etmeye gerek yoktur.


1

Sevdiğim ASIHTTPRequest .


ASIHTTPRequest'teki 'bloklar' uygulamasını gerçekten seviyorum - tıpkı Java'daki Anonim İç Türler gibi. Bu, kod temizliği ve organizasyon açısından diğer tüm çözümleri geride bırakıyor.
Matt Lyons

1

Diğer cevapların da belirttiği gibi, connectionInfo'yu bir yerde saklamalı ve bağlantı yoluyla aramalısınız.

Bunun için en doğal veri türü NSMutableDictionary, ancak NSURLConnectionbağlantılar kopyalanamadığı için anahtar olarak kabul edemez .

NSURLConnectionsAnahtar olarak kullanmak için başka bir seçenek NSMutableDictionarykullanmaktır NSValue valueWithNonretainedObject]:

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

0

NSURLConnection alt sınıfına girmeye ve bir etiket, temsilci ve NSMutabaleData eklemeye karar verdim. İstekler dahil tüm veri yönetimini işleyen bir DataController sınıfım var. Bir DataControllerDelegate protokolü oluşturdum, böylece tek tek görünümler / nesneler, isteklerinin ne zaman bittiğini ve gerekirse ne kadarının indirildiğini veya hata yaptığını öğrenmek için DataController'ı dinleyebilir. DataController sınıfı, yeni bir istek başlatmak için NSURLConnection alt sınıfını kullanabilir ve isteğin ne zaman bittiğini bilmek için DataController'ı dinlemek isteyen temsilciyi kaydedebilir. Bu, XCode 4.5.2 ve ios 6'daki çalışma çözümüm.

DataControllerDelegate protokolünü bildiren DataController.h dosyası). DataController ayrıca bir tekildir:

@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 dosyasındaki temel yöntemler:

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

Ve bir istek başlatmak için: [[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

Ve 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

Her NSURLConnection'ın bir hash niteliği vardır, hepsini bu öznitelikle ayırt edebilirsiniz.

Örneğin, bağlantıdan önce ve sonra belirli bilgileri korumam gerekiyor, bu nedenle RequestManager'ım bunu yapmak için bir NSMutableDictionary'a sahip.

Bir örnek:

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

Talep üzerine:

- (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];
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.