Question

i'm trying to establish an FTP connection within an app. i want to upload several files to a FTP server, all files in one directory. So at first i want to create the remote directory.

- (void) createRemoteDir {

    NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];

    CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
    assert(writeStreamRef != NULL);

    ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
    BOOL success = [ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
    if (success) {
        NSLog(@"\tsuccessfully set the user name");
    }
    success = [ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];
    if (success) {
        NSLog(@"\tsuccessfully set the password");
    }

    ftpStream.delegate = self;
    [ftpStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // open stream
    [ftpStream open];
}

This code doesn't work when executed in a background thread using the following call:

[self performSelectorInBackground: @selector(createRemoteDir) withObject: nil];

my guess is that the (background-threads) runloop isn't running? If i send the message inside the main thread the uploading just works fine:

[self createRemoteDir];

as the runloop of the main thread is up and running.

but fairly large files are going to be uploaded; so i want to put that workload in a background thread. but how and where do i set up the NSRunLoop, so that the whole uploading happens in a background thread? Apples documentation on NSRunLoops (especially how to start them without using a timer/input source, as in this case) didn't help me out.

Was it helpful?

Solution

I found/created a solution that at least works for me. with the above method (createRemoteDir), the following code applied and worked for me:

NSError *error;

createdDirectory = FALSE;
/* 
 only 'prepares' the stream for upload 
 - doesn't actually upload anything until the runloop of this background thread is run
 */
[self createRemoteDir];

NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];

do {

    if(![currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]) {

        // log error if the runloop invocation failed
        error = [[NSError alloc] initWithDomain: @"org.mJae.FTPUploadTrial" 
                                           code: 23 
                                       userInfo: nil];
    }

} while (!createdDirectory && !error);

// close stream, remove from runloop
[ftpStream close];
[ftpStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];

if (error) {
    // handle error
}

It runs in a background thread and creates the directory on the ftp server. I like it more than other examples where runloops are only run for an assumed small interval, say 1second.

[NSDate distantFuture]

is a date in the futur (several centuries, according to Apples documentation). But that's good as the "break-condition" is handled by my class property createdDirectory - or the occurance of an error while starting the runloop.

I can't explain why it works without me explicitly attaching an input source to the runloop (NSTimer or NSPort), but my guess is, it is sufficient that the NSOutputStream is scheduled in the runloop of the background thread (see createRemoteDir).

OTHER TIPS

You could also try to use a dispatch_async call to perform your createRemoteDir in the background. It's much simpler to use and you won't have to worry about managing extra threads.

Here's what the code would look like:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self createRemoteDir];
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top