【问题标题】:db executeUpdate... in FMDB block and doesn't go over, without errordb executeUpdate... 在 FMDB 块中并且没有结束,没有错误
【发布时间】:2013-08-15 10:54:55
【问题描述】:

我在开发中的应用程序中使用了令人惊叹的 FMDB 项目,我有一个这样的 NSOperation:

- (void)main
{
 @autoreleasepool {

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:@"pathDB"]];

[queue inDatabase:^(FMDatabase *db) {

    FMResultSet *toQuery;

    if (self._id == nil) {
        toQuery = [db executeQuery:@"SELECT id,language,update_time FROM task"];

    while ([toQuery next]) {

        [myarray addObject:[toQuery resultDictionary]];

    }
}];

for (int i = 0; i<[myarray count]; i++){

...Do Something

[queue inDatabase:^(FMDatabase *db) {

FMResultSet *checkImgQuery = [db executeQuery:@"SELECT img_url,img_path FROM task WHERE id = ? AND language = ?",myTask.id ,myTask.lang];

while ([checkImgQuery next]) {

 if (![[checkImgQuery stringForColumn:@"img_url"] isEqualToString:myTask.img]) {
                                    NSData *my_img = [NSData dataWithContentsOfURL:[NSURL URLWithString:myTask.img]];

if (my_img != nil) {

NSError *er;

[my_img writeToFile:[checkImgQuery stringForColumn:@"img_path"] options:NSDataWritingAtomic error:&er];

//In the line under here the code block, the app still running, but this operation doesn't
//go over this task

[db executeUpdate:@"UPDATE task SET img_url = ? WHERE id = ? AND language = ?",myTask.img,[NSNumber numberWithInt:myTask.id],[NSNumber numberWithInt:myTask.language];

NSLog(@"%@",[db lastErrorMessage]);
}
...Do Something
}
}
}
}];
}
}

问题出在[db executeUpdate:...] 中,有时可以正常工作,有时会冻结并且不会越过那条线,我放在那里的NSLog 没有打印任何内容,应用程序不会崩溃并继续工作,但线程卡在那里,如果我关闭应用程序的运行,然后我再次重新启动它,线程不会在同一个任务上停止,而是在另一个任务上随机停止,没有标准,有时一个工作,一些时间不...有人可以帮忙吗?

【问题讨论】:

    标签: ios objective-c sqlite fmdb


    【解决方案1】:

    我突然想到了几个问题,其中一个或多个可能会导致您的问题:

    1. 我注意到您正在本地创建 FMDatabaseQueue 对象。您应该只为整个应用程序共享一个 FMDatabaseQueue 对象(我把它放在一个单例中)。数据库队列的目的是协调数据库交互,如果您在各处创建新的FMDatabaseQueue 对象,它就无法合理地做到这一点。

    2. 我建议不要使用 inDatabase 块,在该块中您从网络同步下载一堆图像。

      当您提交 inDatabase 任务时,任何 inDatabase 使用相同的 FMDatabaseQueue 调用其他线程(并且它们应该使用相同的队列,否则您将违背在第一个队列中的目的地方)将不会继续,直到你的操作中运行的那个(反之亦然)。

      在由FMDatabaseQueue 串行队列协调的多个线程进行数据库交互时,您确实希望确保尽快“进出”。不要在 inDatabase 块的中间嵌入可能很慢的网络调用,否则所有其他数据库交互将被阻止,直到它完成。

      所以,请使用inDatabase 来识别需要下载的图像,仅此而已。然后在inDatabase 调用之外,检索您的图像,如果您需要更新图像路径等,请单独调用inDatabase 来执行此操作。但不要在 inDatabase 块中包含任何缓慢和同步的内容。

    3. 我还注意到您正在对task 表执行SELECT,保持FMRecordSet 处于打开状态,然后尝试更新相同的记录。在尝试更新在记录集中检索到的同一条记录之前,您希望打开记录集、检索所需内容并关闭该记录集。

      在尝试更新同一记录的executeUpdate 之前,请始终关闭FMResultSet

    4. 有点不相关,但我可能建议您考虑在原始的 SELECT 语句中包含 img_urlimg_path,这样您的字典条目数组就已经包含了您需要的所有内容,并且可以节省您的时间必须做第二个SELECT


    如果您想知道FMDatabaseQueue 单例可能是什么样子,您可能有一个DatabaseManager 单例,其界面如下所示:

    //  DatabaseManager.h
    
    @import Foundation;
    @import SQLite3;
    
    #import "FMDB.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface DatabaseManager : NSObject
    
    @property (nonatomic, strong, readonly) FMDatabaseQueue *queue;
    
    @property (class, readonly, strong) DatabaseManager *sharedManager;
    
    - (id)init __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
    + (id)new __attribute__((unavailable("Use +[DatabaseManager sharedManager] instead")));
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    实现可能如下所示:

    //  DatabaseManager.m
    
    #import "DatabaseManager.h"
    
    @implementation DatabaseManager
    
    + (DatabaseManager *)sharedManager {
        static id sharedMyManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedMyManager = [[self alloc] init];
        });
        return sharedMyManager;
    }
    
    - (id)init {
        if ((self = [super init])) {
            _queue = [[FMDatabaseQueue alloc] initWithPath:[[NSUserDefaults standardUserDefaults] valueForKey:@"pathDB"]];
        }
        return self;
    }
    
    @end
    

    然后,任何需要与数据库交互的代码都可以像这样检索队列:

    FMDatabaseQueue *queue = DatabaseManager.sharedManager.queue;
    

    【讨论】:

    • 非常感谢您提供完整的答案,我会尝试您写的所有这些建议,我还有另一个问题,我的应用程序中已经有一个单例,用于管理 NSOperationQueue 和请求到服务器,我必须将 FMDatabaseQueue 放在我已经拥有的单例中,或者我可以创建一个新的单例 DatabaseManager?
    • @Piero 任何对您的设计最有意义的东西。如果此操作队列仅用于与数据库相关的交互,那么将其添加到其中可能是合乎逻辑的。但从技术上讲,这并不重要:关键是确保您只有一个 FMDatabaseQueue 对象,无论您如何实现它。
    • @Piero 是的,您可以使用多种类型的单例(例如,一种用于您的操作队列,一种用于您的 FMDatabaseQueue,等等)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    • 2018-06-21
    • 2012-11-13
    • 1970-01-01
    • 2012-06-13
    • 2022-06-30
    相关资源
    最近更新 更多