我的应用需要来自sqlite数据库的数据。它将附带此数据库的一个版本,但我需要定期更新它(很可能每月一次)。通常情况下,我一直在通过我设置的一堆网络服务将我的应用程序的其他部分的更新作为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
,这只是未知错误。这样做之后我的fileData
是nil
。我注意到这篇文章很老了,所以我不知道它是否仍然受到支持。
我还使用NSURLConnection
代替dataWithContentsOfURL
找到了与身份验证问题here不同的解决方案。这是更近期的,但在该示例中使用了一些我以前没有见过的更先进的语法。我能够从示例中复制粘贴,我的项目将构建没有错误,但我不知道使用fetchURL:withCompletion:failure:
UIViewController
例程的正确语法。我试过了
NSError *error;
NSData *fileData;
ExampleDelegate *download = [[ExampleDelegate alloc] init];
[download fetchURL:url withCompletion:fileData failure:&error];
但是这会导致发送不兼容指针的构建错误。我认为我无法分别将NSData
和NSError
作为ExampleDelegateSuccess
和ExampleDelegateFailure
对象传递,即使它们是typedef
编辑的内容。我可能需要对fileData
和error
做一些事情来转换它们,但typedef
是我上面提到的“高级语法”之一,而我不是真的知道该怎么做。
它没有包含在他的示例中,但我找到了另一个名为NSURLConnectionDelegate
here的connection:didReceiveAuthenticationChallenge:
方法,我认为我可以将其添加到示例中的代码中,这应该可以解决我的问题验证问题。我觉得如果我知道如何打电话fetchURL
我会被设定。有没有人对我需要做些什么来获得fileData
和error
进入该电话?
答案 0 :(得分:4)
我认为您的问题源于要求对您的服务器进行身份验证。
我建议使用热门AFNetworking库来获取HTTP调用的帮助。您可以将AFHTTPClient
子类化,以便提供身份验证详细信息,如this answer中所示。
然后在您的情况下,您可以使用AFXMLRequestOperation
直接下载XML。然后,您可以将其写入磁盘。请注意,您需要阅读iOS Data Storage Guidelines以查看Documents文件夹是否适合您的使用(我认为它不适用;缓存可能会更好)。
现在,您的ExampleDelegate
代码(看起来来自here)不起作用的原因是ExampleDelegateSuccess
和ExampleDelegateFailure
是块类型,所以你无法通过您的NSData
和NSError *
指针。您需要提供符合相应类型签名的块对象。
例如:
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]}
];
这些块将根据需要为您提供NSData
和NSError
个实例,因此您无需声明自己的实例。您可以详细了解块here。
希望有所帮助:D
答案 1 :(得分:3)
我们有大型数据库,我们会定期更新。我不确定“大”有多大,但我们正在更新数兆字节的数据。我们将我们更新为JSON数据。 JSON的开销略低于XML,并且有许多现成的库(如JSONKit)。我们的一些用户的数据连接很差,有时会遇到困难。
我们一直在测试不同的更新方式。一种方法将数据分成多个JSON文件。每个文件单独下载,然后分别存储。使用多个文件的一个优点是,如果一个文件失败,您只需要重新发送该特定文件。
此外,我们使用多个线程一次处理多达5个请求/下载。这需要更多的管理,但有助于我们为用户提供更好的体验。 AFHTTPClient非常善于处理这些东西。我讨厌不使用它。
答案 2 :(得分:3)
我最终使用AFNetworking来解决我的问题,正如@silver_belt所建议的那样。我能够在没有继承AFHTTPClient
的情况下做到这一点(参见我的回答here,我发布了这个问题的后续跟踪)。
最后,我只需要在我的视图控制器的.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 :(得分:1)
每次都是一个全新的/不同的数据库,还是同一个数据库的更新版本?
如果是后者,您是否考虑过仅发送更改(通过添加/更新/删除的明确列表,或使用Zumero等自动更新)?