2009-05-10 6 views
8

Ich habe gerade mit der Entwicklung auf Macs begonnen und Cocoa als nützliches und durchdachtes Framework gefunden, aber seine HTTP-Funktionalität hat mich verwirrt.Atomare, asynchrone HTTP-Datei POST mit sensibler Rückmeldung?

Ich habe ein NSURLConnection-Objekt, um eine Datei von meinem Webserver mit der HTTP GET-Methode herunterzuladen. Die asynchrone Verbindung von NSURLConnect ist großartig, ich bekomme viel Feedback, ich bekomme jeden empfangenen Chunk als neues NSData-Objekt, mit dem ich die Datei auf dem Client-Ende atomar neu erstellen kann und, um dem Benutzer einen Fortschrittsbericht zu geben: [myData length ].

Uploads sind jedoch nicht annähernd so sauber. Sie können entweder eine synchrone Anfrage in einen eigenen Thread stecken oder eine asynchrone Anfrage (von der ich glaube, dass sie einen eigenen Thread erzeugt) aufrufen, aber Sie erhalten keine nützliche Rückmeldung. Es gibt keine Delegierten, die Daten anfordern oder mich wissen lassen, wenn Daten gesendet werden. Vermutlich beschränkt dies mich auf Dateien, die kleiner sind als der verfügbare Speicher.

Meine Frage ist daher, gibt es eine einfache und elegante Lösung für HTTP-POST-Datei-Uploads mit Cocoa, die eine gute Menge an Feedback und die Fähigkeit, Dateien zu lesen, Teil für Teil und nicht auf einmal? Oder sollte ich meine eigene Klasse aus Low-Level-Netzwerkfunktionalität schreiben?

Danke!

+0

Warum lassen Sie sie nicht in ihren eigenen Threads gehen? Sie können NSOperation verwenden, um sicherzustellen, dass Sie/etc nicht erstellen/verwalten. Tonnen von Fäden, die Sie nicht brauchen. Welche Art von Datenfeedback erwarten Sie von einem POST-Vorgang? –

+0

Die Menge der Daten, die ich bisher hochgeladen habe. Ich habe es mit potenziell großen Dateien zu tun, daher muss ich dem Benutzer einen Fortschrittsbalken oder Ähnliches bereitstellen. Threading ist nicht so sehr das Problem, es ist vielmehr die Möglichkeit, Dateien - Teil für Teil - zu laden und Feedback zu erhalten. Requisiten für die absurd schnelle Antwort! – Dani

+0

Ah, okay! Ich würde wahrscheinlich selbst etwas rollen. Ich bin mir ziemlich sicher, dass ich einmal eine anständige Klasse für NSURL mit POST-Funktionalität gesehen habe, aber ich kann mich nicht erinnern, wo ... vielleicht jemand eine anständige Antwort hinzufügen wird, aber vielleicht möchten Sie es ein wenig googeln. Ich erinnere mich, dass ich ein bisschen gegraben habe, um es selbst zu finden. –

Antwort

2

Ich entschied mich, mit CFNetwork-Funktionen statt NSURLConnection zu gehen. Bei asynchronen Benachrichtigungen und bestimmten Funktionen (z. B. bei der Authentifizierung) scheint es etwas flexibler zu sein. Leider ist es ein wenig komplizierter (Laufschleife zum Beispiel blase meinen Geist), so empfehle ich Ihnen, das CFNetwork Referenzhandbuch lesen, wenn Sie diesen Weg gehen:

http://developer.apple.com/documentation/Networking/Conceptual/CFNetwork/Introduction/Introduction.html

Hier ist ein Ausschnitt von Code aus meiner POST-Routine, FWIW :

// Create our URL 
CFStringRef url = CFSTR("Submit"); 
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, baseUrl); 

// Create the message request (POST) 
CFStringRef requestMethod = CFSTR("POST"); 
CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, kCFHTTPVersion1_1); 

// Connect the read socket to the HTTP request stream 
CFReadStreamRef myReadStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, myRequest, readStream); 
// TODO: why does this have to be done? 
succ &= CFReadStreamSetClient(myReadStream, 
    kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 
    (CFReadStreamClientCallBack) &MyReadCallBack, &myClientContext); 
CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 
succ &= CFReadStreamOpen(myReadStream); 

1

Leider bist du ganz richtig, dass NSURLConnection hier schwach ist. Der flexibelste Ansatz, den ich empfehlen würde, ist CocoaAsyncSocket. Es bedeutet, dass Sie Ihr eigenes HTTP-Protokoll erstellen, was bedauerlich, aber in den meisten Fällen nicht so schwierig ist. CocoaHTTPServer zeigt, wie Sie einen vollständigen HTTP-Server oberhalb von CocoaAsyncSocket erstellen und möglicherweise viel nützlichen Code für Ihr Problem haben. Ich habe beide sehr nützlich gefunden.

Ein anderer Ansatz, der sich zu untersuchen lohnt, ist WebKit. Erstellen Sie ein unsichtbares WebView und loadRequest: ein POST. Ich habe nicht geklärt, ob das geschätzte Change-Benachrichtigungssystem die Zeit zum Hochladen oder nur die Zeit zum Herunterladen enthält, aber es ist einen Versuch wert.

1

Sie können einen Blick auf den Abschnitt HTTPMessage von my toolkit repository on github für einen einfachen ObjC-Wrapper um CFHTTPMessageRef; unter anderem wird Ihnen ein NSInputStream-Objekt zur Verfügung gestellt, mit dem Sie über Call-Back-Funktionen für einfache Call-Cards nachdenken können.

Je nachdem, was Sie gerade lesen, sollten Sie sich den Abschnitt StreamingXMLParser des gleichen Repositorys für einen XML- (und HTML-) Parser ansehen, der in Ihrem Auftrag Daten direkt von NSInputStream analysiert.

5

Sie können sich das ASIHTTPRequest Framework anschauen. Ich habe es nicht zum Hochladen verwendet, aber es sieht so aus, als hätte es mehr Feedback und die Verwendung ist ziemlich einfach.

2

ASIHTTPRequest wurde ursprünglich nur für diesen Zweck entwickelt (Tracking POST-Fortschritt), da dies in der 2.x-API mit NSURLConnection nicht möglich ist.Es wird definitiv einfacher zu integrieren sein als das eigene mit CFNetwork zu rollen, und Sie erhalten viele andere Dinge kostenlos (z. B. Fortschrittsverfolgung über mehrere Anfragen, Fortsetzen von Downloads usw.). :)

Wenn die Dateien, die Sie hochladen, groß sind, achten Sie darauf, die Optionen für das Streaming direkt von der Festplatte zu betrachten, damit Sie die Daten nicht im Speicher halten müssen.