【问题标题】:Updating sqlite database without XML在没有 XML 的情况下更新 sqlite 数据库
【发布时间】:2013-04-15 12:53:27
【问题描述】:

我的应用需要来自 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 找到了身份验证问题here 的不同解决方案。这是较新的,但在该示例中使用了一些我以前从未真正见过的更高级的语法。我几乎可以从示例中复制粘贴,并且我的项目将毫无错误地构建,但我不知道使用UIViewController 中的fetchURL:withCompletion:failure: 例程的正确语法。我试过了

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

但是发送不兼容的指针会导致构建错误。我想我不能将NSDataNSError 分别作为ExampleDelegateSuccessExampleDelegateFailure 对象传递,即使它们来自typedef。我可能必须对fileDataerror 做一些事情来转换它们,但是typedefing 是我上面提到的“高级外观语法”之一,我真的不知道该怎么做。

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

【问题讨论】:

    标签: ios objective-c sqlite updates


    【解决方案1】:

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

    我推荐流行的AFNetworking 库来帮助进行 HTTP 调用。您可以继承 AFHTTPClient 以提供您的身份验证详细信息,如 this answer

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

    现在,您的ExampleDelegate 代码(看起来像是来自here)不起作用的原因是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 实例,因此您无需声明自己的实例。你可以了解更多关于区块here的信息。

    希望对你有所帮助:D

    【讨论】:

    • 很好的答案。关于积木的部分正是我想要的。我无法验证这是否有效,但由于赏金可能会在我再次查看之前到期,所以我现在将其奖励给你。不过我以后可能会有更多问题。
    • 很高兴我能帮上忙。如果它没有按预期工作,请务必联系,我很乐意与您一起解决。
    • 你绝对让我走上正轨。我最终做了一个完整的 180 并从 ExampleDelegate 代码移开(它在后面产生了其他错误)并改用 AFNetworking。由于我不打算进行 XML 下载,因此我不得不做的事情与您建议的有所不同(请参阅我的答案)。感谢您的所有帮助!
    【解决方案2】:

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

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

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

    【讨论】:

    • 感谢您的建议。我在这里看到了很多关于 JSON 的信息,但我自己还没有使用它——我会看看它(以及你提到的 AFHTTPClient)。我考虑将我的数据库分成多个文件,但几乎可以保证它会不断增长,所以我应该把它分成多少块有点难以弄清楚。我希望找到一条不同的路线,但这可能是唯一的方法。
    • 希望你一切顺利。
    【解决方案3】:

    按照@silver_belt 的建议,我最终使用AFNetworking 来解决我的问题。我可以在没有子类化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];
    

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 是一个更新版本,但是和上一个版本相比会有很多记录发生变化。我相信我记得在某处读过以这种方式更新单个字段的计算成本非常高,并且通常不鼓励,但我可能错了。我对 Zumero 不熟悉,我会调查一下。
      • +1:好建议:仅发送更改可能意味着 MB 的下载节省,而不是再次发送如此大的数据库...
      • 更新“一堆”条目与批量替换相比,确实会花费更多(CPU 和文件系统 I/O),但“一堆”可能占很大比例。你应该测量它。不过,发送 delta 几乎总是会减少网络流量,而且 iOS 设备在慢速蜂窝网络上的速度往往比其他平台要多得多,因此花费更多 CPU 来处理 20 秒的下载会感觉比花费更少的 CPU 来处理要快得多30 秒下载。我再次敦促进行一些测试(包括 WiFi 开/关、LTE 开/关等)
      猜你喜欢
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-06
      • 2015-04-22
      • 2012-04-18
      相关资源
      最近更新 更多