6

我的应用程序需要来自 sqlite 数据库的数据。它将附带该数据库的一个版本,但我需要定期更新它(很可能每月一次)。通常,我一直在通过我设置的一堆 Web 服务将我的应用程序的其他部分作为 XML 发送更新,但是我现在正在处理的这个特定数据库非常大(大约 20-30 MB),而且我当我尝试以这种方式发送时,我收到超时错误。

我尝试将数据库放在我公司的服务器上,然后将其下载到一个NSData对象中。然后我将该数据对象保存到我的应用程序的 Documents 目录中。当我重新启动我的应用程序时,我收到一条错误消息“路径中的文件似乎不是 SQLite 数据库”。代码如下。

// Download data
NSURL *url = [NSURL URLWithString:@"http://www.mysite.net/download/myDatabase.sqlite"];
NSData *fileData = [[NSData alloc] initWithContentsOfURL:url];
NSLog(@"%@",fileData); // Writes a bunch of 8 character (hex?) strings

// Delete old database
NSError *error;
NSURL *destination = [app applicationDocumentsDirectory];
destination = [destination URLByAppendingPathComponent:@"myDatabase"];
destination = [destination URLByAppendingPathExtension:@"sqlite"];
NSLog(@"destination = %@",destination);
[[NSFileManager defaultManager] removeItemAtPath:[destination path] error:&error];

// Save into correct location
[fileData writeToURL:destination atomically:YES];
//[NSFileManager defaultManager] createFileAtPath:[destination path] contents:fileData attributes:nil]; // I also tried this, it doesn't work either.

是否有更新应用程序将使用的大型数据库的标准程序?我觉得我正在尝试做的事情是不正确的,并且有一种完全不同的方法可以做到这一点,但我不知道是什么。


更新:我尝试了几件事来试图找到问题,但没有什么能让我更接近。我将尝试从服务器下载的数据库放到 USB 驱动器上,然后放到我正在开发的 mac 上,然后放到模拟器中我的应用程序的 Documents 文件夹中。当我通过这种方式传输数据库来运行我的应用程序时,它运行没有任何问题,我可以看到我的所有数据。

相反,我从模拟器中完全删除了我的应用程序并重新运行它,因此它将从头开始创建我的数据库。我用 USB 驱动器将这个新制作的数据库从我的 mac 转移到了我的服务器上。一旦文件在我的服务器上,我尝试在上面的代码块中运行我的“下载更新”,但是下次我启动我的应用程序时它仍然崩溃,并出现“路径上的文件似乎不是 SQLite 数据库”错误信息。

从这些测试中,我确信问题不在于我尝试下载的数据库。我的“下载更新”代码中的某些内容正在破坏数据库,但我仍然无法弄清楚原因,也没有找到可接受的替代方法来在我的应用程序启动后将我的更新发送给我的用户。


更新2:我一直在做更多的搜索,我了解到用户需要输入用户名和密码才能访问我服务器上的任何文件。我现在相信NSData我从上面的代码中得到的实际上是一个身份验证挑战,或者与此相关的东西,这就是为什么当我将它保存为它时,NSFileManager它不会被识别为 sqlite DB。

我发现通过身份验证的最简单的解决方案是here。当我尝试这样做时NSData *fileData = [NSData dataWithContentsOfURL:url options:NSDataReadingMapped error:&error];(当然,在按照链接中的建议更改我的之后url),我得到一个错误NSCocoaErrorDomain Code=256,这只是未知错误。我fileDatanil在这样做之后。我注意到这篇文章很老了,所以我不知道它是否仍然支持。

我还找到了一个不同的解决方案来解决这里的身份验证问题,使用一个NSURLConnection而不是dataWithContentsOfURL. 这是较新的,但在该示例中使用了一些我以前从未真正见过的更高级的语法。我几乎可以从示例中复制粘贴,并且我的项目将毫无错误地构建,但我不知道使用fetchURL:withCompletion:failure:我的UIViewController. 我试过了

NSError *error;
NSData *fileData;
ExampleDelegate *download = [[ExampleDelegate alloc] init];
[download fetchURL:url withCompletion:fileData failure:&error];

但这会导致发送不兼容的指针的构建错误。我想我不能分别传递一个直接的NSDataNSErrorasExampleDelegateSuccessExampleDelegateFailure对象,即使它们是typedef从什么来的。我可能需要做一些事情来fileData转换error它们,但是typedefing 是我上面提到的“高级外观语法”之一,我真的不知道该怎么做。

它没有包含在他的示例中,但我发现了另NSURLConnectionDelegate一种称为connection:didReceiveAuthenticationChallenge: 此处的方法,我想我可以将其添加到示例中的代码中,这应该可以解决我的身份验证问题。我觉得如果我只知道如何打电话fetchURL,我就会被设置。有人对我需要做些什么来接听电话有什么建议fileDataerror

4

4 回答 4

4

我认为您的问题源于需要对您的服务器进行身份验证。

我推荐流行的AFNetworking库来帮助进行 HTTP 调用。您将子类化AFHTTPClient以提供您的身份验证详细信息,如本答案中所示。

然后在您的情况下,您可以使用AFXMLRequestOperation直接下载您的 XML。然后,您可以将其写入磁盘。请注意,您需要阅读iOS 数据存储指南以查看 Documents 文件夹是否适合您使用(我认为不适合;缓存可能会更好)。

现在,您的ExampleDelegate代码(看起来像是来自这里)不起作用的原因是它是块类型ExampleDelegateSuccessExampleDelegateFailure因此您不能传递您的NSDataNSError *指针。您需要提供符合相应类型签名的块对象。

例如:

ExampleDelegate *download = [[ExampleDelegate alloc] init];
[download fetchURL:url 
          withCompletion:^(NSData *thedata) {
    NSLog(@"Success! Data length: %d", theData.length);

    // Delete old database
    NSError *error;

    // You need to declare 'app' here so you can use it in the line below.
    NSURL *destination = [app applicationDocumentsDirectory];
    destination = [destination URLByAppendingPathComponent:@"myDatabase"];
    destination = [destination URLByAppendingPathExtension:@"sqlite"];
    NSLog(@"destination = %@",destination);
    [[NSFileManager defaultManager] removeItemAtPath:[destination path] error:&error];

    // Save into correct location
    BOOL success = [fileData writeToURL:destination atomically:YES];
    NSLog(@"File written successfully? %d", success);
} 
          failure:^(NSError *theError){ 
    NSLog(@"Failure: %@", [theError localizedDescription]}
];

这些块将根据需要为您提供NSDataNSError实例,因此您无需声明自己的。您可以在此处了解有关块的更多信息。

希望有帮助:D

于 2013-05-06T00:24:20.160 回答
3

正如@silver_belt 所建议的那样,我最终最终使用AFNetworking来解决我的问题。不过,我可以在没有子类化的情况下做到这一点AFHTTPClient(请参阅我的答案here,我将其作为该问题的后续内容发布)。

最后,我只需要#include "AFHTTPRequestOperation.h"在我的视图控制器的 .h 文件中,然后在我使用的 .m 中

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"ftp://myUsername:myPassword@www.mysite.net/myfile.sqlite"]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSURL *path = [[[app applicationDocumentsDirectory] URLByAppendingPathComponent:@"myfile"] URLByAppendingPathExtension:@"sqlite"];
operation.outputStream = [NSOutputStream outputStreamWithURL:path append:NO];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
    {
        NSLog(@"Successfully downloaded file to path: %@",path);
    }
                                 failure:^(AFHTTPRequestOperation *operation, NSError *error)
    {
        NSLog(@"Error in AFHTTPRequestOperation in clickedButtonAtIndex on FlightInfoController.m");
        NSLog(@"%@",error.description);
    }];

[operation start];

当我真正想开始下载时。希望这将使任何希望在未来这样做的人更容易!

于 2013-05-02T13:59:37.557 回答
3

我们拥有定期更新的大型数据库。我不确定“大”有多大,但我们正在更新兆字节的数据。我们将我们的更新为 JSON 数据。JSON 的开销比 XML 少一点,并且有很多现成的库(如 JSONKit)。我们的一些用户数据连接不佳,有时会遇到困难。

我们一直在测试不同的更新方式。一种方法将数据分解为多个 JSON 文件。每个文件都单独下载,然后单独存储。使用多个文件的一个优点是,如果一个文件失败,您只需要重新发送该特定文件。

此外,我们使用多个线程一次处理最多 5 个请求/下载。这需要更多的管理,但有助于我们为用户提供更好的体验。AFHTTPClient 非常擅长处理这些东西。我讨厌不使用它。

于 2013-04-23T04:40:12.143 回答
1

每次都是全新/不同的数据库,还是同一数据库的更新版本?

如果是后者,您是否考虑过仅发送更改(通过添加/更新/删除的明确列表,或使用 Zumero 之类的东西来自动更新)?

于 2013-04-30T18:25:36.237 回答