objective-c - 如何从 OS X 应用程序中的 URL 下载文件

标签 objective-c xcode macos

如何从 OS X 应用程序中的 URL 下载文件?我是一个刚接触 Objective C 的 Unix C 和 Perl 程序员。我一直在阅读 URL Loading System Programming Guide ,它建议 NSURLSession 用于新的 OS X 应用程序。所以我更喜欢使用 NSURLSession 或 NSURLSeesionDownloadTask 的解决方案,但我对 NSURLDownload 或其他解决方案持开放态度。

我的第一次尝试是基于找到的一个例子 here

        NSURLSessionDownloadTask *downloadTask =
        [[NSURLSession sharedSession]
         downloadTaskWithURL:[NSURL URLWithString:pdfFile]
         completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {

             // move tmp file to permanent location
             NSLog(@"Done...");
             NSLog(@"Location:   %@", location);
             NSLog(@"Response:   %@", response);
             NSLog(@"Error       %@", error);

             NSFileManager *fileManager = [NSFileManager defaultManager];
             BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:[appDir stringByAppendingString:@"/demo.pdf"] error:&error];
             NSLog(fileCopied ? @"File Copied OK" : @"ERROR Copying file.");
            }];
    [downloadTask resume];
    NSLog(@"Now we are here");

示例代码(和我的版本)没有单独的委托(delegate)。 completionHandler 是“在线的”。 (不确定这是否是正确的术语)。我假设此代码将在下载任务完成后执行。对吗?

我的第二次尝试是基于 URL 编程指南的“下载文件”部分:

    // PDF file Download, try #2
    NSError *error = nil;
    NSString *appDir = NSHomeDirectory();
    NSString *pdfFile = @"https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/AppDistributionGuide.pdf";

    // Configure Cache behavior for default session
    NSURLSessionConfiguration
    *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSString *cachePath = [NSTemporaryDirectory()
                           stringByAppendingPathComponent:@"/pdfDownload.cache"];
    NSLog(@"Cache path: %@\n", cachePath);
    NSURLCache *myCache = [[NSURLCache alloc] initWithMemoryCapacity: 16384
                                                        diskCapacity: 268435456 diskPath: cachePath];
    defaultConfigObject.URLCache = myCache;
    defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    defaultConfigObject.timeoutIntervalForRequest = 100;
    defaultConfigObject.timeoutIntervalForResource = 100;

    // Create a delegate-Free Session
    NSURLSession *delegateFreeSession =
        [NSURLSession sessionWithConfiguration: defaultConfigObject
                                      delegate: nil
                                 delegateQueue: [NSOperationQueue mainQueue]];

    [[delegateFreeSession downloadTaskWithURL:[NSURL URLWithString:pdfFile] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {

        // move tmp file to permanent location
        NSLog(@"Done...");
        NSLog(@"Location:   %@", location);
        NSLog(@"Response:   %@", response);
        NSLog(@"Error       %@", error);

        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:[appDir stringByAppendingString:@"/demo.pdf"] error:&error];
        NSLog(fileCopied ? @"File Copied OK" : @"ERROR Copying file.");
    }] resume];

    sleep(10);
    NSLog(@"After sleep...");

在这两种情况下,代码执行永远不会到达作为 completionHandler 一部分的 NSLog 语句。我在模拟器中设置了断点,但从未到达那里。如何创建一个简单的“无委托(delegate)”文件下载方法?

因为我正在编写“命令行”OS X 应用程序,所以我没有 UIViewController 或其他现有的委托(delegate)结构。

关于如何进行的任何建议?我不需要下载进度条等,只需要“通过/失败”状态即可。

另外,我这里的示例是针对单个 PDF 文件的。我的应用程序有一组我想要下载的 URL。我不需要并行下载它们。我可以在循环中重复使用单个 NSURLSession 吗?

如有任何帮助,我们将不胜感激!

最佳答案

得到解决方案后忘记关闭它。正如 Zev 所指出的,我选择的 PDF 文件很大,需要更多时间来下载。 10 秒的“ sleep (10)”没有提供足够的时间来下载 39 MB 的文件。

通知应该用于指示 completionHandler 何时完成。但是现在,我正在使用一个简单的轮询循环。我改变了 sleep (10);到

int  timeoutCounter = 60;
while ((timeoutCounter > 0) && !downloadDone) {
    sleep(1);
    NSLog(@"Timout: %d", timeoutCounter--);
}

if (!downloadDone) {
    NSLog(@"ERROR: Timeout occurred before file completed downloading");
}

这需要声明 downloadDone 以允许 completionHandler block 设置它:

    __block BOOL downloadDone = FALSE;

NSFileManger 移动文件后,必须设置此信号量:

     downloadDone = TRUE;

再次感谢 Zev 的帮助!

我仍在进行错误检查/处理,但这里是完整的更正方法:

+ (NSError *)           downloadFileAtURL:(NSString *)fileURL
                                toDir:(NSString *)toDir
                              timeout:(NSNumber *)timeout {

// completion handler semaphore to indicate download is done
__block BOOL downloadDone = FALSE;

// same PDF file name for later
NSString *downloadFileName = [fileURL lastPathComponent];
NSString *finalFilePath = [toDir stringByAppendingPathComponent:downloadFileName];
NSLog(@"PDF file name: '%@'", downloadFileName);
NSLog(@"Final file path: '%@'", finalFilePath);

// Configure Cache behavior for default session
NSURLSessionConfiguration
*defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *cachePath = [NSTemporaryDirectory()
                       stringByAppendingPathComponent:@"/downloadFileCache"];
NSLog(@"Cache path: %@\n", cachePath);
NSURLCache *myCache = [[NSURLCache alloc] initWithMemoryCapacity: 16384
                                                    diskCapacity: 268435456 diskPath: cachePath];
defaultConfigObject.URLCache = myCache;
defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
defaultConfigObject.timeoutIntervalForRequest = 100;
defaultConfigObject.timeoutIntervalForResource = 100;

// Create a delegate-Free Session
//      delegateQueue: [NSOperationQueue mainQueue]];
NSURLSession *delegateFreeSession =
[NSURLSession sessionWithConfiguration: defaultConfigObject
                              delegate: nil
                         delegateQueue: nil];

[[delegateFreeSession downloadTaskWithURL:[NSURL URLWithString:fileURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    
    // move tmp file to permanent location
    NSLog(@"In the completion handler...");
    NSLog(@"Location:   %@", location);
    NSLog(@"Response:   %@", response);
    NSLog(@"Error       %@", error);
    NSLog(@"Location path: %@", [location path]);
    
    // Verify temp file exists in cache
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    if ( [fileManager fileExistsAtPath:[location path]]) {
        NSLog(@"File exists.  Now move it!");
        
        error = nil;
        BOOL fileCopied = [fileManager moveItemAtPath:[location path]
                                               toPath:finalFilePath
                                                error:&error];
        NSLog(@"Error: %@", error);
        NSLog(fileCopied ? @"File Copied OK" : @"ERROR Copying file.");
    }
    downloadDone = TRUE;
    
}] resume];

// use timeout argument
int  timeoutCounter = [timeout intValue];
while ((timeoutCounter > 0) && !downloadDone) {
    sleep(1);
    NSLog(@"Timout: %d", timeoutCounter--);
}

if (!downloadDone) {
    NSLog(@"ERROR: Timeout occurred before file completed downloading");
}

NSError *error = nil;
return error;
}

关于objective-c - 如何从 OS X 应用程序中的 URL 下载文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28418122/

相关文章:

objective-c - '+系统字体大小:' not found

iPhone 项目未运行(多次)

c++ - 不会创建 Makefile

java - 我能否在 Mac OS X 上为 Swing 获得原生外观的 JPopupMenu 外观?

macos - 使用 golang 进行 Mac 交叉编译(或替代方案)

ios - NSPredicate 过滤器对多与子对象属性

objective-c - 如何直接调用dispatch_block_t?

objective-c - 使用 NSPredicate 使用 2D-NSDictionaries 过滤 NSArray

iphone - 在 UITableViewCell 中绘制文本

cocoa - 绑定(bind)和 NSButton