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 的火花这么慢?他们有啥替代方案吗?