FMDB 阻止用户界面。但为啥?对替代实施有啥建议吗?

Posted

技术标签:

【中文标题】FMDB 阻止用户界面。但为啥?对替代实施有啥建议吗?【英文标题】:FMDB Blocking UI. But why? Any suggestions for alternative implementation?FMDB 阻止用户界面。但为什么?对替代实施有什么建议吗? 【发布时间】:2014-03-26 15:43:42 【问题描述】:

我有一个使用 FMDB 的应用程序,并在应用程序启动后立即执行更新(仅一次)。更新非常繁重,需要 12-20 秒来处理。我将 FMDatabaseQueue 与基于单例类的事务一起使用。

======== DB.h ====================

@interface DB : NSObject
    FMDatabaseQueue *dbqueue;
    NSArray *queriesCreateSchema;
    NSString *dbFullPath;


@property (nonatomic, strong) FMDatabaseQueue *dbqueue;

===================================

======== DB.m =====================

- (id)initWithFullPath: (NSString*)fullPath 
    if (self = [super init]) 

        dbFullPath = fullPath;

        //Opening/Creating the serial queue
        dbqueue = [FMDatabaseQueue databaseQueueWithPath:dbFullPath];
        if (dbqueue == nil)
            return nil;
        

        queriesCreateSchema = [NSArray arrayWithObjects:DBQUERY_ENABLE_FOREIGN_KEYS,
                               DBQUERY_CREATE_DB,
                               DBQUERY_CREATE_USERS,
                               DBQUERY_CREATE_BOOKS,
                               DBQUERY_INDEX_BOOKS,
                               DBQUERY_CREATE_BOOKUSER,
                               DBQUERY_CREATE_PAGES,
                               DBQUERY_CREATE_PAGEUSER,
                               DBQUERY_CREATE_TAGS,
                               DBQUERY_CREATE_TAGBOOK,
                               DBQUERY_CREATE_CATEGORIES,
                               DBQUERY_CREATE_CATBOOK,
                               nil];
    
    return self;

=================================== ======== DBManager.h =====================

@interface DBManager : NSObject <CommsManagerDelegate> 
    __weak id <DBMDelegate>  delegate;
    DB *database;
    NSString *dbFullPath;



@property (nonatomic, weak) id <DBMDelegate> delegate;
@property (nonatomic, strong) DB *database;
@property (nonatomic, strong) NSString *dbFullPath;

==========================================

======== DBManager.m ====================

-(BOOL) parseMetaDataDict: (NSDictionary*) serverDict 

    /// Getting the lists of books from the Server's JSON dictionary
    NSDictionary *dictAllBooks = [serverDict objectForKey:@"Books"];

    int bookNum=0;
    int totalBooks = [[dictAllBooks valueForKey:@"Book"] count];

    // Updates the UI
    [delegate dbmNumberOfBooksProcessedByDB:totalBooks];

    /// Browsing it book by book
    for (id serverDictBook in [dictAllBooks valueForKey:@"Book"])
        bookNum++;

        /// Trimming book from the server and placing it into the local book dictionary
        BookDict *bookDict = [[BookDict alloc]initWithServerDict:serverDictBook];

        __block BOOL isError = NO;

        /// Sending the queries into the serial queue
        [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
            /// Inserting book into the BOOKS table
            if(![db executeUpdate:DBUPDATE_INSERT_BOOK withParameterDictionary:bookDict.dictionary])
            
                isError = YES;
                DDLogWarn(@"%@", [db lastErrorMessage]);
                *rollback = YES;
                return;      // Carefull - It returns from the transaction, not the function
            
        ];

        if (isError)
            return NO;
        

        __block NSString *bookID;

        /// Getting the bookID automatically generated by the DB
        NSString *query = [NSString stringWithFormat:@"SELECT bookID FROM BOOKS where isbn = '%@'", [bookDict.dictionary valueForKey:@"isbn"]];
        [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
            FMResultSet *result = [db executeQuery:query];
            if([result next])
            
                int num = [result intForColumnIndex:0];
                bookID = [NSString stringWithFormat:@"%d", num];
            
            else
                isError = YES;
                DDLogWarn(@"%@", [db lastErrorMessage]);
                *rollback = YES;
                return;      // Carefull - It returns from the transaction, not the function
            
        ];

        if (isError)
            return NO;
        


        int numPages = [[serverDictBook objectForKey:@"numberOfPages"] intValue];

        /// Browsing the book page by page
        ///VCC Today probably replace by 0
        for (int i=1; i<=numPages; i++)
        
            PageDict *pageDict = [[PageDict alloc]initWithPage:i andBookID:bookID ofServerDict:serverDictBook];

            __block BOOL isError = NO;

            /// Sending the queries into the serial queue
            [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
                /// Inserting page into the PAGES table
                if(![db executeUpdate:DBUPDATE_INSERT_PAGE withParameterDictionary:pageDict.dictionary])
                
                    isError = YES;
                    DDLogWarn(@"%@", [db lastErrorMessage]);
                    *rollback = YES;
                    return;      // Carefull - It returns from the transaction, not the function
                
            ];

            if (isError)
            return NO;
        


        __block NSString *catID;

        /// Browsing the book categories one by one
        for (id serverCatDict in [serverDictBook valueForKey:@"categories"])

            __block BOOL isError = NO;

            /// Sending the queries into the serial queue
            [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
                /// Inserting row into the CATEGORY table
                if(![db executeUpdate:DBUPDATE_INSERT_CATEGORY withParameterDictionary:serverCatDict])
                
                    isError = YES;
                    DDLogWarn(@"%@", [db lastErrorMessage]);
                    *rollback = YES;
                    return;      // Carefull - It returns from the transaction, not the function
                

                /// Getting the catID automatically generated by the DB
                NSString *query = [NSString stringWithFormat:@"SELECT catID FROM CATEGORIES where name = '%@'", [serverCatDict valueForKey:@"name"]];

                FMResultSet *result = [db executeQuery:query];
                if([result next])
                
                  catID = [result stringForColumnIndex:0];
                
                else
                  isError = YES;
                  DDLogError(@"%@", [db lastErrorMessage]);
                  *rollback = YES;
                  return;      // Carefull - It returns from the transaction, not the function
                

              CatBookDict *catBookDict = [[CatBookDict alloc] initWithCatID:catID andBookID:bookID];

              /// Inserting row into the CATBOOK table
              if(![db executeUpdate:DBUPDATE_INSERT_CATBOOK withParameterDictionary:catBookDict.dictionary])
              
                isError = YES;
                DDLogError(@"%@", [db lastErrorMessage]);
                *rollback = YES;
                return;      // Carefull - It returns from the transaction, not the function
              

          ];

          if (isError)
              return NO;

        


//      /// Browsing the book categories one by one
//      for (id serverCatDict in [serverDictBook valueForKey:@"name"])
//        
//          __block BOOL isError = NO;
//
//        CatBookDict *catBookDict = [[CatBookDict alloc] initWithCatID:[serverCatDict valueForKey:@"catID"]];
//
//          /// Sending the queries into the serial queue
//          [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
//                                  andBookID:bookID];
//              /// Inserting row into the CATBOOK table
//              if(![db executeUpdate:DBUPDATE_INSERT_CATBOOK withParameterDictionary:catBookDict.dictionary])
//              
//                isError = YES;
//                DDLogVerbose(@"%@", [db lastErrorMessage]);
//                *rollback = YES;
//                return;      // Carefull - It returns from the transaction, not the function
//              
//          ];
//                                      
//          if (isError)
//          return NO;
//
//      
      [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
        FMResultSet *result = [db executeQuery:query];
        if([result next])
        
          int num = [result intForColumnIndex:0];
          bookID = [NSString stringWithFormat:@"%d", num];
        
        else
          isError = YES;
          DDLogError(@"%@", [db lastErrorMessage]);
          *rollback = YES;
          return;      // Carefull - It returns from the transaction, not the function
        
      ];

      if (isError)
        return NO;
      



        /// Browsing the book tags one by one
        for (id serverTagDict in [serverDictBook valueForKey:@"tags"])
//            TagDict *tagDict = [[TagDict alloc] initWithServerDict:serverTagDict[0]];
//            TagBookDict *tagBookDict = [[TagBookDict alloc] initWithTagID:[serverTagDict valueForKey:@"tagID"]
//                                                                andBookID:bookID];
            __block BOOL isError = NO;

            /// Sending the queries into the serial queue
            [database.dbqueue inTransaction:^(FMDatabase *db, BOOL *rollback) 
                /// Inserting tag into the TAGS table
                if(![db executeUpdate:DBUPDATE_INSERT_TAG withParameterDictionary:serverTagDict])
                
                    isError = YES;
                    DDLogError(@"%@", [db lastErrorMessage]);
                    *rollback = YES;
                    return;      // Carefull - It returns from the transaction, not the function
                

//                /// Inserting the row into the TAGBOOK table
//                if(![db executeUpdate:DBUPDATE_INSERT_TAGBOOK withParameterDictionary:tagBookDict.dictionary])
//                
//                    isError = YES;
//                    DDLogVerbose(@"%@", [db lastErrorMessage]);
//                    *rollback = YES;
//                    return;      // Carefull - It returns from the transaction, not the function
//                

            ];

            if (isError)
                return NO;
        

        // Updates the UI
        [delegate dbmBookProcessedByDB:bookNum];

    

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if (![defaults objectForKey:@"firstSynced"])
        [defaults setObject:[NSDate date] forKey:@"firstSynced"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    

    return TRUE;

我有一个视图控制器,它通过调用前面的方法“parseMetaDataDict”来使用 DBManager 类。对于一本书的每次迭代都进行处理,并且插入发生在数据库中。我在循环中使用委托和更新 UI。

============= SplashViewController.h ============================

-(void)dbmBookProcessedByDB:(int)bookNum

    dispatch_async(dispatch_get_main_queue(), ^(void)
        //Run UI Updates
        NSString *strMsg = [NSString stringWithFormat:@"DB Processing Book %d / %d", bookNum, _totalBooksToBeDownloaded];
        [self.progressBar setText:strMsg];;

        if (bookNum == _totalBooksToBeDownloaded)
                [self.progressBar setText:@"Books library has successfully been updated"];
                [self.progressBar setNeedsDisplay];
                [self performSegueWithIdentifier:@"splashToHome" sender:self];
        

    );


==========================================

进度条没有更新。我相信 dispatch_async 是不必要的。调试显示它通过了 dispatch_async 并且永远不会进入内部。

是 FMDB 队列阻塞了整个 Main 线程。每次处理一本书时,如何对标签进行定期更新?

【问题讨论】:

【参考方案1】:

在 FMDB 中,dispatch_sync 函数用于将您的事务块放入串行队列。 Documentation for dispatch_sync 说:

作为一种优化,此函数调用当前块 尽可能线程。

我认为这就是为什么调用-inTransaction: 可能会阻塞主线程。

尝试让-inTransaction: 被后台线程调用。为此,您可以通过 CGD 将 for 循环的主体放入 background queue,如下所示:

-(BOOL) parseMetaDataDict: (NSDictionary*) serverDict 
    ...

    /// Browsing it book by book
    for (id serverDictBook in [dictAllBooks valueForKey:@"Book"])
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void)

            ... all -inTransaction: calls are here

            dispatch_async(dispatch_get_main_queue(), ^(void)
                // Updates the UI
                [delegate dbmBookProcessedByDB:bookNum];
            );

        );
    

注意:最好在一个范围内的线程之间跳转,以使代码看起来清晰,因此您也可以将dispatch_async(dispatch_get_main_queue(), ...)-dbmBookProcessedByDB 移动到for 正文中,如上面的代码所示。

【讨论】:

以上是关于FMDB 阻止用户界面。但为啥?对替代实施有啥建议吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 sortByKey 的火花这么慢?他们有啥替代方案吗?

为啥预处理器宏是邪恶的,有啥替代方案?

FMDB 阻止了我的 UI

为啥我可以阻止基元而不是用户定义类型的隐式转换?

为啥 AUNetSend 在 iOS 上不可用? (或任何替代建议)

使用 Amazon S3 阻止公有访问