不使用XML更新sqlite数据库

6
我的应用程序需要从SQLite数据库中获取数据。它将使用此数据库的版本,但我需要定期更新它(最可能每月更新一次)。通常,我通过一堆设置好的web服务以XML的方式发送应用程序其他部分的更新,但是我现在正在处理的这个特定的数据库相当大(约20-30 MB),并且我尝试以这种方式发送时,我会出现超时错误。
我尝试将数据库放在我的公司服务器上,然后将其下载到NSData对象中。然后,我将该数据对象保存到我的应用程序文档目录中。当我重新启动应用程序时,会出现错误,提示“路径处的文件似乎不是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电脑上,并将其放到了Simulator中我的应用程序的Documents文件夹中。当我通过这种方式传输数据库运行我的应用程序时,它可以正常运行,我可以看到所有的数据。

相反,我完全从模拟器中删除了我的应用程序,并重新运行它,以便从头开始创建我的数据库。我将这个新建的数据库从我的Mac电脑传输到了我的服务器上。一旦文件在我的服务器上,我就尝试在上面运行我的“下载更新”代码块,但它仍然会在下一次启动我的应用程序时崩溃,并显示“路径上的文件似乎不是SQLite数据库”的错误消息。

通过这些测试,我确信问题不在于我尝试下载的数据库。我的“下载更新”代码中的某些内容正在损坏数据库,但我仍然无法找出原因,也没有找到可接受的替代方法来在我的应用程序启动后向用户提供更新。


更新2: 我进行了更多的搜索,并且了解到用户需要输入用户名和密码才能访问我的服务器上的任何文件。我现在相信,我从上面的代码中获取的NSData实际上是一个认证挑战或类似于此的内容,这就是为什么当我用NSFileManager保存它时,它不被识别为sqlite数据库。

我发现的最简单的解决方案是在这里。当我尝试执行NSData *fileData = [NSData dataWithContentsOfURL:url options:NSDataReadingMapped error:&error];(当然,在更改了我的url之后),我会收到错误NSCocoaErrorDomain Code=256,这只是未知错误。做完这个操作后,我的fileDatanil。我注意到这篇文章很古老,所以我不知道它是否仍然受支持。

我还发现了另一种解决身份验证问题的方法,这里使用了一个NSURLConnection而不是dataWithContentsOfURL。这个例子比较新,但其中有一些更高级的语法使我之前从未见过。我基本上可以从例子中复制粘贴,我的项目能够成功构建,但我不知道如何在我的UIViewController中正确使用fetchURL:withCompletion:failure:方法的语法。我尝试了:
NSError *error;
NSData *fileData;
ExampleDelegate *download = [[ExampleDelegate alloc] init];
[download fetchURL:url withCompletion:fileData failure:&error];

但是这会导致构建错误,因为发送了不兼容的指针。我认为我不能直接将 NSDataNSError 作为分别对应于 ExampleDelegateSuccessExampleDelegateFailure 对象传递,即使它们是从中 typedef 的。我可能需要对 fileDataerror 进行一些转换,但是那个 typedef 是我上面提到的 "高级外观语法" 之一,我不太清楚该怎么做。

虽然他的示例中没有包含,但我在这里找到了另一个名为 connection:didReceiveAuthenticationChallenge:NSURLConnectionDelegate 方法,我认为我可以将其添加到示例代码中,这样就可以解决我的身份验证问题。我觉得如果我知道如何调用 fetchURL,我就可以解决了。有人能给我关于如何将 fileDataerror 传递给该调用的建议吗?

4个回答

4
我认为你的问题源于需要在服务器上进行身份验证。
我建议使用流行的AFNetworking库来帮助进行HTTP调用。您需要子类化AFHTTPClient以提供身份验证详细信息,例如this answer
然后,在您的情况下,您可以使用AFXMLRequestOperation直接下载XML。然后,您可以将其写入磁盘。请注意,您需要阅读iOS数据存储指南,以确定文档文件夹是否适合您的用途(我认为不适合;Caches可能更好)。
现在,你的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


很棒的答案。关于块的部分正是我在寻找的。我还不能验证这是否有效,但由于赏金可能会在我再次查看之前到期,所以我现在将其授予给您。不过,我以后可能还会有更多问题。 - GeneralMike
很高兴我能帮忙。如果出现意料之外的情况,请随时与我联系,我很乐意和您一起解决问题。 - silver_belt
你肯定让我走上了正确的道路。最终,我进行了彻底的改变,放弃了 ExampleDelegate 代码(它在后面创建了其他错误),转而使用 AFNetworking。由于我并不想进行 XML 下载,所以我的做法与你的建议有些不同(请看我的回答)。非常感谢你的帮助! - GeneralMike

3
我最终使用AFNetworking解决了我的问题,正如@silver_belt建议的那样。但是我不需要子类化AFHTTPClient(请参见我在此问题的后续中发布的答案)。

最后,我只需要在我的视图控制器的.h文件中包含#include "AFHTTPRequestOperation.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];

当我实际想要开始下载时。希望这能让未来任何想要这样做的人更容易!

3
我们拥有大型数据库,定期更新。我不确定“大”有多大,但我们正在更新以兆字节计算的数据。我们将数据更新为JSON格式。与XML相比,JSON的开销较小,并且有许多现成的库(例如JSONKit)。一些用户因数据连接差而遇到困难。
我们一直在测试不同的更新方法。其中一种方法是将数据分解成多个JSON文件。每个文件都单独下载,然后分别存储。与使用多个文件相比的一个优点是,如果其中一个失败了,您只需要重新发送该特定文件。
此外,我们使用多线程同时处理最多5个请求/下载。这需要更多的管理,但可以帮助我们为用户提供更好的体验。AFHTTPClient非常擅长处理这些问题。我不想不使用它。

谢谢您的建议。我在这里看到了很多关于JSON的内容,但是自己还没有使用过——我会去看一下它(以及您提到的AFHTTPClient)。我考虑过将我的数据库分成多个文件,但它几乎肯定会继续增长,所以应该将其切成多少个部分有点难以确定。我希望能找到另一种解决方法,但这可能是唯一的方法。 - GeneralMike
希望你一切顺利。 - HalR

1

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

如果是后者,您是否考虑仅发送更改内容(通过明确的添加/更新/删除列表或使用类似Zumero的工具自动化更新)?


这是一个更新版本,但自上一版本以来有许多记录已经更改。我记得在某个地方读到过这样更新单个字段的方法会非常耗费计算资源,通常不被鼓励,但我可能错了。我不熟悉Zumero,我会去了解一下。 - GeneralMike
+1:好建议:仅发送更改可能意味着与再次发送整个数据库相比可以节省数MB的下载流量。 - JRG-Developer
更新“一堆”条目的成本(CPU和文件系统I/O)往往比批量替换更高,但“一堆”可以占相当大的比例。您应该进行测量。发送增量几乎总是会产生较少的网络流量,而iOS设备往往在缓慢的蜂窝网络上运行得更多,因此花费更多的CPU来处理20秒的下载会感觉比花费更少的CPU来处理30秒的下载要快得多。再次建议进行一些测试(包括WiFi开/关、LTE开/关等)。 - Stripes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接