Sending large amounts of data with NSFileHandle - objective-c

So I'm using Apple's PictureSharing/PictureSharingBrowser samples to send and receive data. This uses an NSFileHandle on the server side to send a picture using NSFileHandle's writeData method.
NSFileHandle * incomingConnection = [[aNotification userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
[[aNotification object] acceptConnectionInBackgroundAndNotify];
[incomingConnection writeData:dataToWrite];
[incomingConnection closeFile];
This seems to work fine until I want to send large amounts of data (in this case 1MB worth of data). When I attempt this, the application hangs while executing the writeData method. The client doesn't even begin reading the data, it simply opens the connection, but nothing happens. (it's supposed to read the data chunk by chunk, while the server sends all teh data at once).
I'm guessing some deadlock is occurring somewhere, but i'm not sure where. I tried to look for an async. way of writing the data chuck by chuck with NSFileHandle, but i could not find such a way.
Any guidance would help!

I missed one step basically... in NSNetServiceBrowser's netServiceBrowser: didFindService:( moreComing: delegate method, instead of me simply trying to connect to every incoming service, I instead (as the doc says :) ) retain the service, set the delegate for that found service, and attempt to resolve the service.
I am then able to open a stream to the resolved service in *- (void)netServiceDidResolveAddress:(NSNetService )sender which is NSNetservice's delegate method.
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
[aNetService retain];
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:5.0];
}
- (void)netServiceDidResolveAddress:(NSNetService *)service{
NSInputStream * istream;
[sender getInputStream:&istream outputStream:nil];
[istream retain];
[istream setDelegate:self];
[istream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[istream open];
[service release];
}
//... NSStreamDelegate method to retrieve the data via the stream.

Related

How do I wait for a large file to be downloaded?

I have an app that successfully uses the synchronous methods to download files (NSData's initWithContentsOfURL and NSURLConnection's sendSynchronousRequest), but now I need to support large files. This means I need to stream to disk bit by bit. Even though streaming to disk and becoming asynchronous should be completely orthoganal concepts, Apple's API forces me to go asynchronous in order to stream.
To be clear, I am tasked with allowing larger file downloads, not with re-architecting the whole app to be more asynchronous-friendly. I don't have the resources. But I acknowledge that the approaches that depend on re-architecting are valid and good.
So, if I do this:
NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
.. I eventually have didReceiveResponse and didReceiveData called on myself. Excellent. But, if I try to do this:
NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
while( !self.downloadComplete )
[ NSThread sleepForTimeInterval: .25 ];
... didReceiveResponse and didReceiveData are never called. And I've figured out why. Weirdly, the asynchronous download happens in the same main thread that I'm using. So when I sleep the main thread, I'm also sleeping the thing doing the work. Anyway, I have tried several different ways to achieve what I want here, including telling the NSURLConnection to use a different NSOperationQueue, and even doing dispatch_async to create the connection and start it manually (I don't see how this couldn't work - I must not have done it right), but nothing seems to work. Edit: What I wasn't doing right was understanding how Run Loops work, and that you need to run them manually in secondary threads.
What is the best way to wait until the file is done downloading?
Edit 3, working code:
The following code actually works, but let me know if there's a better way.
Code executing in the original thread that sets up the connection and waits for the download to complete:
dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_async(downloadQueue, ^{
self.connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
[ [ NSRunLoop currentRunLoop ] run ];
});
while( !self.downloadComplete )
[ NSThread sleepForTimeInterval: .25 ];
Code executing in the new thread that responds to connection events:
-(void)connection:(NSURLConnection*) connection didReceiveData:(NSData *)data {
NSUInteger remainingBytes = [ data length ];
while( remainingBytes > 0 ) {
NSUInteger bytesWritten = [ self.fileWritingStream write: [ data bytes ] maxLength: remainingBytes ];
if( bytesWritten == -1 /*error*/ ) {
self.downloadComplete = YES;
self.successful = NO;
NSLog( #"Stream error: %#", self.fileWritingStream.streamError );
[ connection cancel ];
return;
}
remainingBytes -= bytesWritten;
}
}
-(void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error {
self.downloadComplete = YES;
[ self.fileWritingStream close ];
self.successful = NO;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.downloadComplete = YES;
[ self.fileWritingStream close ];
self.successful = YES;
}
... didReceiveResponse and didReceiveData are never called. And I've
figured out why. Weirdly, the asynchronous download happens in the
same main thread that I'm using. It doesn't create a new thread. So
when I sleep the main thread, I'm also sleeping the thing doing the
work.
Exactly. The connection is driven by the run loop; if you sleep the thread, the run loop stops, and that prevents your connection from doing its thing.
So don't do anything special. Let the app sit there, with the run loop running. Maybe put a little spinner on the screen to entertain the user. Go about your business if you can. If at all possible, let the user continue to use the application. Your delegate method will be called when the connection is complete, and then you can do what you need to do with the data.
When you move your code to a background thread, you'll again need a run loop to drive the connection. So you'll start create a run loop, schedule your connection, and then just return. The run loop will keep running, and your delegate method will again be called when the connection completes. If the thread is done, you can then stop the run loop and let the thread exit. That's all there is to it.
Example: Let's put this in concrete terms. Let's say that you want to make a number of connections, one at a time. Stick the URL's in a mutable array. Create a method called (for example) startNextConnection that does the following things:
grabs an URL from the array (removing it in the process)
creates an URL request
starts a NSURLConnection
return
Also, implement the necessary NSURLConnectionDelegate methods, notably connectionDidFinishLoading:. Have that method do the following:
stash the data somewhere (write it to a file, hand it to another thread for parsing, whatever)
call startNextConnection
return
If errors never happened, that'd be enough to retrieve the data for all the URLs in your list. (Of course, you'll want startNextConnection to be smart enough to just return when the list is empty.) But errors do happen, so you'll have to think about how to deal with them. If a connection fails, do you want to stop the entire process? If so, just have your connection:didFailWithError: method do something appropriate, but don't have it call startNextConnection. Do you want to skip to the next URL on the list if there's an error? Then have ...didFailWithError: call startNextRequest.
Alternative: If you really want to keep the sequential structure of your synchronous code, so that you've got something like:
[self downloadURLs];
[self waitForDownloadsToFinish];
[self processData];
...
then you'll have to do the downloading in a different thread so that you're free to block the current thread. If that's what you want, then set up the download thread with a run loop. Next, create the connection using -initWithRequest:delegate:startImmediately: as you've been doing, but pass NO in the last parameter. Use -scheduleInRunLoop:forMode: to add the connection to the download thread's run loop, and then start the connection with the -start method. This leaves you free to sleep the current thread. Have the connection delegate's completion routine set a flag such as the self.downloadComplete flag in your example.
I hesitate to provide this answer because the others are correct that you really should structure your app around the asynchronous model. Nevertheless:
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSString* myPrivateMode = #"com.yourcompany.yourapp.DownloadMode";
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:myPrivateMode];
[connection start];
while (!self.downloadComplete)
[[NSRunLoop currentRunLoop] runMode:myPrivateMode beforeDate:[NSDate distantFuture]];
Do not do this on the main thread. Your app is just as likely to be terminated for blocking the main thread as for downloading too big a file to memory.
By the way, given that you're downloading to a file instead of memory, you should consider switching from NSURLConnection to NSURLDownload.
I think your sleepForInterval is blocking the NSURLConnection's activity -
No run loop processing occurs while the thread is blocked.
From the NSThread documentation.
I think you might have to rethink how you're setting your downloadComplete variable. Consider using your connectionDidFinishLoading:connection delegate method to determine when the download is complete instead of your loop + sleep?
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.downloadComplete = YES;
// release the connection, and the data object
[connection release];
[receivedData release];
}
From the NSURLConnection guide.
You can use the connection:connection didFailWithError:error delegate method to ensure you're dealing with situations where the download does not complete.

Speak to a device using TCP/IP

I try to open a TCP stream to speak to a device with a cocoa app.
I searched the web and found that there is some possibilities to do that but i'm a little bit stuck.
I decided to use the NSStream way (because it's referenced in cocoa-touch, will be usefull if i want to port my app to iPhone i presume), so here is my code:
#implementation AppDelegate
- (IBAction)connect:(id)sender {
[NSStream getStreamsToHost:"192.168.1.4" port:23 inputStream:&inputStream outputStream:&outputStream];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
// Both streams call this when events happen
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
if (aStream == inputStream) {
[self handleInputStreamEvent:eventCode];
} else if (aStream == outputStream) {
[self handleOutputStreamEvent:eventCode];
}
}
- (void)handleInputStreamEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable:[self readBytes];
break;
case NSStreamEventOpenCompleted:
// Do Something
break;
default:
case NSStreamEventErrorOccurred:
NSLog(#"An error occurred on the input stream.");
break;
}
}
So, when I click on my connect button, it is supposed to open the stream to my host and make my 2 objects (inputstream and outputstream)
The first step I would like to reach is to have the inputStream in a NSTextView and know if the host has been reached or not... but i'm still stuck :(
If someone can light my way, it would be nice! I'm new on Stack Overflow and I'll be glad to help the community on somethings that i know much! :)
I updated my code and it seems that the light is coming, slowly but it's coming :)
I made a stream to a telnet server. I got the "hello" in a texview.
Now, I would like to send the user & password to be able to send commands to the server, but here is my "send user & pass" button code:
- (IBAction)sendusername:(id)sender {
NSString * usernameMsg = [NSString stringWithFormat:#"user #", [usernameField stringValue]];
NSData * usertosend = [[NSData alloc] initWithData:[usernameMsg dataUsingEncoding:NSUTF8StringEncoding]];
[outputStream write:[usertosend bytes] maxLength:[usertosend length]];
}
Follow my searchs, the server should respond me a thing like "user +ok" but nothing...
2 stranges things:
- If I open the socket to a FTP server of SSH server, I've always the "hello" response without problem. But in telnet, 90% of connections respond me a strange hello like this: "ÿýÿýÿûÿû", why?
When I send the user, nothing happen, only an unrocognized event from the handleEvent...
I can suggest you to look at https://github.com/robbiehanson/CocoaAsyncSocket/, a nice Objective-C wrapper for BSD sockets. It allows you to handle the send-receive interactions on the event loop in nice and clean callback manner (it even handles custom "message terminating" symbols for you allowing to concentrate more on actual packet handling rather then on combining and splitting the stuff you receive from wire).

S3RequestDelegate Methods aren't running for S3GetObjectRequest in AWS IOS SDK 1.0.1

I wrote a quick objective-C method that uses Amazon's AWS iOS SDK to synchronously download a file from my Amazon S3 Bucket in my iPad app. This is an enterprise app, and I am using reachability to detect WiFi before allowing synchronization with S3. It is working fine for short downloads (those in kilobytes), but with files that are around 20-30 megs, it will continue to download into the stream and the file will continue growing. I've not let it go to see if it will eventually stop/crash, but I've watched a file that was 30 megs go past 90 megs in my iOS Simulator. I've read into several cold threads where some have experienced the same and I really need an answer.
Here is my method...
- (void)retrieveRemoteFile:(NSString *)fileName {
NSString *destinationFileName = [NSString stringWithFormat:#"%#%#",[self getBucketDirectory],fileName];
AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease];
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:destinationFileName append:NO];
[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[stream open];
S3GetObjectRequest *request = [[S3GetObjectRequest alloc] initWithKey:fileName withBucket:BUCKET];
request.outputStream = stream;
[s3 getObject:request];
[stream close];
[stream release];
[request release];
}
Re-evaluating the situation, I'd really like to get my method using an ASynchronous Request and use a S3RequestDelegate object to help me update the bytesIn as it's downloading. In their archive, there is a sample in S3AsyncViewController that should show how to do what I want. I've added S3RequestDelegate.h/.m into my project, and implemented a S3RequestDelegate in my .h like this...
#import "S3RequestDelegate.h"
... {
S3RequestDelegate *s3Delegate;
}
I've altered my retrieveRemoteFile method to look a little like this (it's changed all day and I haven't gotten anywhere)
- (void)retrieveRemoteFile:(NSString *)fileName {
NSString *destinationFileName = [NSString stringWithFormat:#"%#%#",[self getBucketDirectory],fileName];
AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease];
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:destinationFileName append:NO];
//[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//[stream open];
S3GetObjectRequest *request = [[S3GetObjectRequest alloc] initWithKey:fileName withBucket:BUCKET];
request.outputStream = stream;
s3Delegate = [[S3RequestDelegate alloc] init];
[request setDelegate:s3Delegate];
[s3 getObject:request];
//[stream close];
//[stream release];
//[request release];
}
As you can see, I've set the S3GetObjectRequest delegate with setDelegate to my S3RequestDelegate pointer s3Delegate. I've added breakpoints in all of the delegate methods of the S3RequestDelegate object, but none of them are executing. In looking for a received file on my simulator, nothing is even getting downloaded now.
The sample makes it look like all you need to do is set a delegate to make it asynchronous. It also makes it look like you don't need to manage the stream object, and whether you do or not, nothing gets downloaded. I'm setting the delegate and it's never running any of the delegate methods; didReceiveResponse, didCompleteWithResponse, didReceiveData, didSendData, totalBytesExpectedToWrite, didFailWithError or didFailWithServiceException.
I was setting up my request to run in an OperationsQueue. Removed this code and kick off the Asynchronous transfer on the main thread and the delegate functions handle transferring the next file in the array. I've incorporated a status bar and cancel button and it worked out great.

NSURLConnection leak?

i have set up a nsurl which grabs the data from http.
when i run instrument, it says i have a leak NSFNetwork object.
and how do i release theConnection in (void)ButtonClicked? or it will be release later on?
- (void)ButtonClicked {
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:KmlUrl]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0f];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// receivedData is declared as a method instance elsewhere
NSMutableData *receivedData = [[NSMutableData data] retain];
[self setKMLdata:receivedData];
} else {
// inform the user that the download could not be made
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the receivedData
// receivedData is declared as a method instance elsewhere
[KMLdata appendData:data];
NSLog(#"didReceiveData");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// release the connection, and the data object
[connection release];
[KMLdata release];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[KMLdata release];
}
I finally found the answer for this.
The error in the above code (which by the way is the near-exact sample from the SDK docs) is not in the memory management code. Autorelease is one option, manual release is another. Regardless of how you handle your NSURLConnection object, you get leaks using NSURLConnection.
First up, here is the solution. Just copy these 3 lines of code directly into connectionDidFinishLoading, didFailWithError and anywhere else you release the NSURLConnection object.
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
Credit to mpramodjain on http://forums.macrumors.com/showthread.php?t=573253 for the code.
The problem seems to be this – the SDK caches the requests and replies on the iPhone. Even it seems if your NSMutableURLRequest cachePolicy is set to not load the reply from the cache.
The silly thing is that it seems to cache a lot of data by default. I'm transmitting a lot of data (split into multiple connections) and started to get memory warnings, and finally my App died.
The docs we need are in NSURLCache (not NSURLConnection), they state:
NSURLCache implements the caching of
responses to URL load requests by
mapping NSURLRequest objects to
NSCachedURLResponse objects. It is a
composite of an in-memory and an
on-disk cache.
Methods are provided to manipulate the
sizes of each of these caches as well
as to control the path on disk to use
for persistent storage of cache data.
Those three lines have the effect of nuking the cache totally. After adding them to my App (GPS Log), my #living object count remains steady.
Hello have you test this delegate method ?
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
You can manage the cache more precisely.
"reset" NSURLCache *sharedCache can cause problems on other part of your code ?
This is a common question and is solved by the magic of [object autorelease]. In your code this would be as follows:
NSURLConnection *theConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self] autorelease];
In this way, the object is automatically added to the "autorelease pool" and dealloc'd at the start of the next run loop after it is no longer referenced.
Hope that helps
Edit: Also, I don't see why you're needing to call -retain on your receivedData variable.
I am using the static method/autoreleased approach and it appears to work fine:
[NSURLConnection connectionWithRequest:theRequest delegate:self];
This way you don't even have to worry about releasing in the delegate callbacks. It turns out that the retain count of the connection is actually 2 (not 1) after it is alloc'd in the examples above, which changes the way I thought about this memory "leak."
#rpetrich I actually don't think you need to worry about the delegate being released before the connection is released. The connection retains it's delegate and the connection itself is actually retained by some sort of open connections queue. I wrote a blog post on my experiments with NSURLConnection on my blog:
"Potential leak of object" with NSURLConnection

What's wrong on following URLConnection?

See also:
Objective-C Asynchronous Web Request with Cookies
I spent a day writing this code and can anyone tell me what is wrong here?
WSHelper is inherited from NSObject, I even tried NSDocument and NSObjectController and everything..
-(void) loadUrl: (NSString*) urlStr{
url = [[NSURL alloc] initWithString:urlStr];
request = [NSURLRequest requestWithURL:url cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
if(connection)
{
receivedData = [[NSMutableData data] retain];
//[connection start];
}
else
{ display error etc... }
NSApplication * app = [NSApplication sharedApplication];
[app runModalForWindow: waitWindow];// <-- this is the problem...
}
-(void)connection: (NSURLConnection*)connection didReceiveData:(NSData*)data{
progressText = #"Receiving Data...";
[receivedData appendData:data];
}
-(void)connection: (NSURLConnection *)connection didFailWithError:(NSError *)error{
progressText = #"Error...";
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText:[error localizedDescription]];
[alert runModal];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
progressText = #"Done...";
pData = [[NSData alloc] initWithData:receivedData];
[self hideWindow];
}
The code just wont do anything, it doesnt progress at all. I even tried it with/without startImmediately:YES but no luck !!!, this is executed in main window so even the thread and its run loop is running successfully.
I tried calling synchronous request, and it is working correctly !! But I need async solution.
I have added CoreServices.Framework in project, is there anything more I should be adding to the project? any compiler settings? Or do i have to initialize anything before I can use NSURLConnection?
Any solution to run NSURLConnection on different thread on its own NSRunLoop, Objective-C and MAC Development has no sample code anywhere in documentation that makes everything so difficult to code.
I also met the same problem that didn't get the delegate method called when using NSURLConnection in a Modal Window.
after some investigation, following code resolve it.
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:NO];
[conn scheduleRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
[conn start];
However, when connectionDidFinishLoading called, [NSApp stopModal] doesn't work, need call [NSApp abortModal] instead.
Firstly you're making starting the connection too complicated. Change to:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]
Remove [connection start]. Now:
Is your app definitely running the run loop normally? NSURLConnection requires this to work.
Are you able to perform a synchronous load of the URL request?
In the debugger, can you see that url is what you expect it to be? What is it?
Is it possible that you're deallocating WSHelper before any delegate messages are received? NSURLConnection is asynchoronous after all.
One does not need to do anything special to use NSURLConnection, it's a straightforward part of the Foundation framework. No special compiler settings required. No initialization before use. Nothing. Please don't start blindly trying stuff like bringing in CoreServices.Framework.
As sending the request synchronously works, there must be something wrong with your handling of the asynchronous aspect. It could be:
The runloop is not running in NSDefaultRunLoopMode so the connection is unable to schedule itself.
Some other part of your code is calling -cancel on the connection before it has a chance to load.
You are managing to deallocate the connection before it has a chance to load.
Real problem
Ah, in fact I've just realised what's going on. You are calling:
-[NSApp runModalForWindow:]
Read the description of what this method does. It's not running the run loop like NSURLConnection expects. I'd say that really, you don't want to be presenting a window quite like this while running a URL connection for it.
I'd also suggest that you implement the -connection:didReceiveResponse: delegate method too. You want to check here that the server is returning the expected status code.
You say that you're using this in a modal dialog? A modal dialog puts the run loop into a different mode. You should be able to get this to work by scheduling it to run in the modal dialog run loop mode, in addition to the normal run loop mode. Try adding this line of code after you allocate connection in loadURL:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
Hope that helps.
How do you know it isn't doing anything? Are there any error or warning messages during the compile? Are any error messages showing up on console when the program is running?
Have you tries setting breakpoints in your code and following through what you expect to be happening?