FMDB Blocking UI. But why? Any suggestions for alternative implementation? - ios

I have an application that uses FMDB and performs an update as soon as the application starts (only once). The update is quite heavy and takes between 12-20 seconds to process . I am using FMDatabaseQueue with transactions based on a singleton class.
======== 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;
}
I have a view controller that uses the DBManager Class by calling the previous method "parseMetaDataDict". For every iteration of the for a book is processed and inserts take place in the DB. I am using delegation and updating the UI within the loop.
============= 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];
}
});
}
=========================================
The updates to the progress Bar are not taking place. I believe that the dispatch_async is unnecessary. Debugging shows that it passes over the dispatch_async and never enters inside.
Is the FMDB queue blocking the entire Main thread. How can I make periodical updates to a label every time a book is processed?

In FMDB the dispatch_sync function is used to put your transaction blocks into the serial queue.
Documentation for dispatch_sync says:
As an optimization, this function invokes the block on the current
thread when possible.
I think that's why calls to -inTransaction: may block the main thread.
Try to make the -inTransaction: to be called from the background thread. To do this you can put the body of your for cycle into the background queue via CGD like this:
-(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];
});
});
}
Note: It's also better to jump between threads in one scope, to make code look clear, so you can also move dispatch_async(dispatch_get_main_queue(), ...) from -dbmBookProcessedByDB inside for body as shown in code above.

Related

ReactiveCocoa takeUntil 2 possible signals?

So I have successfully turned a button into an off and on switch that changes the label.
I was also able to have it start a timed processed set off when that is to occur, and it have the ability to shut off the timed process.
Anyways I need to way to shut down the timed process I was wondering if there was a way to stop it without using the disposable. With a second takeUntil signal.
Edit I think what I was trying to do was slightly misleading let me show my current solution that works.
-(RACSignal*) startTimer {
return [[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]];
}
-(void) viewWillAppear:(BOOL)animated {}
-(void) viewDidLoad {
self.tableView.delegate = self;
self.tableView.dataSource = self;
RACSignal* pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];
#weakify(self);
RACSignal* textChangeSignal = [pressedStart map:^id(id value) {
#strongify(self);
return [self.start.titleLabel.text isEqualToString:#"Start"] ? #"Stop" : #"Start";
}];
// Changes the title
[textChangeSignal subscribeNext:^(NSString* text) {
#strongify(self);
[self.start setTitle:text forState:UIControlStateNormal];
}];
RACSignal* switchSignal = [[textChangeSignal map:^id(NSString* string) {
return [string isEqualToString:#"Stop"] ? #0 : #1;
}] filter:^BOOL(id value) {
NSLog(#"Switch %#",value);
return [value boolValue];
}];
[[self rac_signalForSelector:#selector(viewWillAppear:)]
subscribeNext:^(id x) {
}];
static NSInteger t = 0;
// Remake's it self once it is on finished.
self.disposable = [[[textChangeSignal filter:^BOOL(NSString* text) {
return [text isEqualToString:#"Stop"] ? [#1 boolValue] : [#0 boolValue];
}]
flattenMap:^RACStream *(id value) {
NSLog(#"Made new Sheduler");
#strongify(self);
return [[self startTimer] takeUntil:switchSignal];
}] subscribeNext:^(id x) {
NSLog(#"%#",x);
#strongify(self);
t = t + 1;
NSLog(#"%zd",t);
[self updateTable];
}];
[[self rac_signalForSelector:#selector(viewWillDisappear:)] subscribeNext:^(id x) {
NSLog(#"viewWillAppear Dispose");
[self.disposable dispose];
}];
}
-(BOOL) isGroupedExcercisesLeft {
BOOL isGroupedLeft = NO;
for (int i =0;i < [self.excercises count]; i++) {
Excercise* ex = [self.excercises objectAtIndex:i];
if(ex.complete == NO && ex.grouped == YES) {
isGroupedLeft = YES;
break;
}
}
return isGroupedLeft;
}
-(void) updateTable {
// Find the
NSInteger nextRow;
if (([self.excercises count] > 0 || self.excercises !=nil) && [self isGroupedExcercisesLeft]) {
for (int i =0;i < [self.excercises count]; i++) {
Excercise* ex = [self.excercises objectAtIndex:i];
if(ex.complete == NO && ex.grouped == YES) {
nextRow = i;
break;
}
}
NSIndexPath* path = [NSIndexPath indexPathForItem:nextRow inSection:0];
NSArray* indexPath = #[path];
// update //
Excercise* ex = [self.excercises objectAtIndex:nextRow];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
if (ex.seconds <= 0) {
RLMRealm* db = [RLMRealm defaultRealm];
[db beginWriteTransaction];
ex.complete = YES;
[db commitWriteTransaction];
}
else {
// Update Seconds
RLMRealm* db = [RLMRealm defaultRealm];
[db beginWriteTransaction];
ex.seconds = ex.seconds - 1000;
NSLog(#"Seconds: %zd",ex.seconds);
[db commitWriteTransaction];
// Update table
[self.tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationNone];
}
} else {
NSLog(#"Done");
SIAlertView *alertView = [[SIAlertView alloc] initWithTitle:#"Deskercise" andMessage:#"Excercises Complete"];
[alertView addButtonWithTitle:#"Ok"
type:SIAlertViewButtonTypeDefault
handler:^(SIAlertView *alert) {
}];
alertView.transitionStyle = SIAlertViewTransitionStyleBounce;
[alertView show];
NSLog(#"Dispose");
[self.disposable dispose];
}
}
The issue with using takeUntil:self.completeSignal is that when you change completeSignal to another value, it isn't passed to any function that was already waiting for the variable that completeSignal was previously holding.
- (RACSignal*) startTimer {
#weakify(self)
return [[[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]]
takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
merge:[[RACObserve(self, completeSignal) skip:1] flattenMap:
^RACStream *(RACSignal * signal) {
#strongify(self)
return self.completeSignal;
}]]
];
}
The signal is now observing and flattening completeSignal, which will give the desired effect. Signals that complete without sending next events are ignored by takeUntil:, so use self.completedSignal = [RACSignal return:nil], which sends a single next event and then completes.
However, this code is anything but ideal, let's look at a better solution.
#property (nonatomic, readwrite) RACSubject * completeSignal;
- (RACSignal*) startTimer {
return [[[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]]
takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
merge:self.completeSignal]
];
}
- (void) viewDidLoad {
[super viewDidLoad];
self.completeSignal = [RACSubject subject];
self.tableView.delegate = self;
self.tableView.dataSource = self;
RACSignal * pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];
#weakify(self);
RACSignal* textChangeSignal = [[pressedStart startWith:nil] scanWithStart:#"Stop" reduce:^id(id running, id next) {
return #{#"Start":#"Stop", #"Stop":#"Start"}[running];
}];
[self.start
rac_liftSelector:#selector(setTitle:forState:)
withSignals:textChangeSignal, [RACSignal return:#(UIControlStateNormal)], nil];
[[[pressedStart flattenMap:^RACStream *(id value) { //Using take:1 so that it doesn't get into a feedback loop
#strongify(self);
return [self startTimer];
}] scanWithStart:#0 reduce:^id(NSNumber * running, NSNumber * next) {
return #(running.unsignedIntegerValue + 1);
}] subscribeNext:^(id x) {
#strongify(self);
[self updateTable];
NSLog(#"%#", x);
}];
}
- (void) updateTable {
//If you uncomment these then it'll cause a feedback loop for the signal that calls updateTable
//[self.start sendActionsForControlEvents:UIControlEventTouchUpInside];
//[self.completeSignal sendNext:nil];
if ([self.excercises count] > 0 || self.excercises !=nil) {
} else {
}
}
Let's run through the list of changes:
completeSignal is now a RACSubject (a manually controlled RACSignal).
For purity and to get rid of the #weakify directive, textChangeSignal now uses the handy scanWithStart:reduce: method, which lets you access an accumulator (this works well for methods that work with an incrementing or decrementing number).
start's text is now being changed by the rac_liftSelector function, which takes RACSignals and unwraps them when all have fired.
Your flattenMap: to replace pressedStart with [self startTimer] now uses scanWithStart:reduce, which is a much more functional way to keep count.
I'm not sure if you were testing by having updateTable contain completion signals but it definitely causes a logic issue with your flattenMap: of pressedButton, the resulting feedback loop eventually crashes the program when the stack overflows.

FMDatabaseQueue Error: database is locked

I have a method that runs in a background thread, and so (as I understand it) I need to use FMDatabaseQueue to safely and reliably access my SQLite database.
I'm doing a query to check for the presence of a record, after which I immediately UPDATE or INSERT depending on the result.
The first query runs fine and I get a count, but then the query that follows doesn't run. Here's the error I get:
Unknown error calling sqlite3_step (5: database is locked) eu
Here is my code:
//Establish database queue
NSString *path = [[PPHelpers documentsPath] stringByAppendingPathComponent:#"PilotPro2.db"];
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
//Start thread-safe database queue
[queue inDatabase:^(FMDatabase *dbq) {
NSUInteger count;
//The other parameters in this query are provided beforehand
NSString *query = [NSString stringWithFormat:#"SELECT COUNT(%#) AS counter FROM %# WHERE %# = '%#'",columnID, model, columnID, dict[columnID]];
FMResultSet *countResult = [dbq executeQuery:query]; //This works fine
while([countResult next]) {
count = [countResult intForColumn:#"counter"];
}
[countResult close];
if(count > 0){
//--- UPDATE
//-- This is where FMDB throws the error...
[dbq executeUpdate:[PPDatabase editAircraftQuery:dict[columnID]], dict[#"aircraftRegistration"], dict[#"makeModel"], dict[#"categoryClass"], dict[#"highPerformance"], dict[#"complex"], dict[#"turbine"], dict[#"turboprop"], dict[#"tailwheel"], dict[#"active"]];
}else{
//--- INSERT
[dbq executeUpdate:[PPDatabase addAircraftQuery], dict[#"aircraftID"], dict[#"aircraftRegistration"], dict[#"makeModel"], dict[#"categoryClass"], dict[#"highPerformance"], dict[#"complex"], dict[#"turbine"], dict[#"turboprop"], dict[#"tailwheel"], dict[#"active"]];
}
}];
Do I need to separate my SELECT query from the others somehow? Any idea why my database is locked after the first query?
I have the same issue. I made sharedInstance with global queue
context.h
#interface context : NSObject
{
FMDatabaseQueue *_queue;
}
+ (context *)sharedInstance;
#property(strong, nonatomic, readwrite) FMDatabaseQueue *queue;
#end
context.m
#import "context.h"
#implementation context
#synthesize queue = _queue;
+ (context *)sharedInstance {
static dispatch_once_t onceToken;
static context *instance = nil;
dispatch_once(&onceToken, ^{
instance = [[context alloc] init];
});
return instance;
}
- (id)init {
self = [super init];
if (self) {
_queue = [FMDatabaseQueue databaseQueueWithPath:YOUR_SQLITE_FILE_PATH];
}
return self;
}
#end
How to use it
context *appContext = [context sharedInstance];
[appContext.queue inDatabase:^(FMDatabase *db) {
FMResultSet *results = [db executeQuery:#"SELECT * FROM something"];
if([results next]) {
NSLog(#"results dump = %#", [results resultDictionary]);
}
[results close];

Loading csv file in Core Data

I am trying to load a csv file in core data when the application is ran for the first time. On another post on stackoverflow found here (What is the fastest way to load a large CSV file into core data), I found out how to do that.
I am using the same code form the provided function: populateDB, in my controller and calling the function if the data has never been loaded before (first run). However, xcode is giving me an error:
No visible #interface for ...Controller declares the selector persistentStoreCoordinator.
The function is:
-(void)populateDB{
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"input" ofType:#"txt"];
if (filePath) {
NSString * myText = [[NSString alloc]
initWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil];
if (myText) {
__block int count = 0;
[myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
line=[line stringByReplacingOccurrencesOfString:#"\t" withString:#" "];
NSArray *lineComponents=[line componentsSeparatedByString:#" "];
if(lineComponents){
if([lineComponents count]==3){
float f=[[lineComponents objectAtIndex:0] floatValue];
NSNumber *number=[NSNumber numberWithFloat:f];
NSString *string1=[lineComponents objectAtIndex:1];
NSString *string2=[lineComponents objectAtIndex:2];
NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:#"Bigram" inManagedObjectContext:context];
[object setValue:number forKey:#"number"];
[object setValue:string1 forKey:#"string1"];
[object setValue:string2 forKey:#"string2"];
NSError *error;
count++;
if(count>=1000){
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
count=0;
}
}
}
}];
NSLog(#"done importing");
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
}
}
I am picking up iOS again after 3 years of absence and I have never dived into this part of the SDK before. I would greatly appreciate any help with this issue...
The code below shows you an example to load csv file in Core Data using the class CSVParser.hof Matt Gallagher and supposing an entity MyEntity
// CSV File input.csv and not input.txt separate by space #" "
CSVParser *csvParser = [[CSVParser alloc] initWithContentOfFile:[NSBundle mainBundle] pathForResource:#"input" ofType:#"csv" separator:#" "];
// Array with all your data from CSV
NSArray *data = [csvParser parseFile];
// Your entity from Core Data
MyEntity *myEntity = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
for (NSDictionary *dic in data)
{
fetchRequest.entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
if (!entity)
entity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity"
inManagedObjectContext:context];
[entity setValue:number forKey:#"number"];
[entity setValue:string1 forKey:#"string1"];
[entity setValue:string2 forKey:#"string2"];
}
// Save the context
[managedObjectContext save:nil];
CVSParser.h using ARC :
//
// CSVParser.h
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
// Source : http://cocoawithlove.com/2009/11/writing-parser-using-nsscanner-csv.html
#interface CSVParser : NSObject
{
NSString *csvString;
NSString *separator;
NSScanner *scanner;
BOOL hasHeader;
NSMutableArray *fieldNames;
id receiver;
SEL receiverSelector;
NSCharacterSet *endTextCharacterSet;
BOOL separatorIsSingleChar;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString;
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (NSArray *)arrayOfParsedRows;
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector;
- (NSArray *)parseFile;
- (NSMutableArray *)parseHeader;
- (NSDictionary *)parseRecord;
- (NSString *)parseName;
- (NSString *)parseField;
- (NSString *)parseEscaped;
- (NSString *)parseNonEscaped;
- (NSString *)parseDoubleQuote;
- (NSString *)parseSeparator;
- (NSString *)parseLineSeparator;
- (NSString *)parseTwoDoubleQuotes;
- (NSString *)parseTextData;
#end
CVSParser.m using ARC :
//
// CSVParser.m
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import "CSVParser.h"
#implementation CSVParser
//
// initWithString:separator:hasHeader:fieldNames:
//
// Parameters:
// aCSVString - the string that will be parsed
// aSeparatorString - the separator (normally "," or "\t")
// header - if YES, treats the first row as a list of field names
// names - a list of field names (will have no effect if header is YES)
//
// returns the initialized object (nil on failure)
//
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
self = [super init];
if (self)
{
csvString = [aCSVString retain];
separator = [aSeparatorString retain];
NSAssert([separator length] > 0 &&
[separator rangeOfString:#"\""].location == NSNotFound &&
[separator rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location == NSNotFound,
#"CSV separator string must not be empty and must not contain the double quote character or newline characters.");
NSMutableCharacterSet *endTextMutableCharacterSet =
[[NSCharacterSet newlineCharacterSet] mutableCopy];
[endTextMutableCharacterSet addCharactersInString:#"\""];
[endTextMutableCharacterSet addCharactersInString:[separator substringToIndex:1]];
endTextCharacterSet = endTextMutableCharacterSet;
if ([separator length] == 1)
{
separatorIsSingleChar = YES;
}
hasHeader = header;
fieldNames = [names mutableCopy];
}
return self;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
{
return [self initWithString:aCSVString
separator:aSeparatorString
hasHeader:YES
fieldNames:nil];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString
hasHeader:header
fieldNames:names];
}
//
// dealloc
//
// Releases instance memory.
//
- (void)dealloc
{
[csvString release];
[separator release];
[fieldNames release];
[endTextCharacterSet release];
[super dealloc];
}
//
// arrayOfParsedRows
//
// Performs a parsing of the csvString, returning the entire result.
//
// returns the array of all parsed row records
//
- (NSArray *)arrayOfParsedRows
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
NSArray *result = [self parseFile];
[scanner release];
scanner = nil;
return result;
}
//
// parseRowsForReceiver:selector:
//
// Performs a parsing of the csvString, sending the entries, 1 row at a time,
// to the receiver.
//
// Parameters:
// aReceiver - the target that will receive each row as it is parsed
// aSelector - the selector that will receive each row as it is parsed
// (should be a method that takes a single NSDictionary argument)
//
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
receiver = [aReceiver retain];
receiverSelector = aSelector;
[self parseFile];
[scanner release];
scanner = nil;
[receiver release];
receiver = nil;
}
//
// parseFile
//
// Attempts to parse a file from the current scan location.
//
// returns the parsed results if successful and receiver is nil, otherwise
// returns nil when done or on failure.
//
- (NSArray *)parseFile
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
if (hasHeader)
{
if (fieldNames)
{
[fieldNames release];
}
fieldNames = [[self parseHeader] retain];
if (!fieldNames || ![self parseLineSeparator])
{
return nil;
}
}
NSMutableArray *records = nil;
if (!receiver)
{
records = [NSMutableArray array];
}
NSDictionary *record = [[self parseRecord] retain];
if (!record)
{
return nil;
}
while (record)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (receiver)
{
[receiver performSelector:receiverSelector withObject:record];
}
else
{
[records addObject:record];
}
[record release];
if (![self parseLineSeparator])
{
break;
}
record = [[self parseRecord] retain];
[pool drain];
}
[scanner release];
scanner = nil;
return records;
}
//
// parseHeader
//
// Attempts to parse a header row from the current scan location.
//
// returns the array of parsed field names or nil on parse failure.
//
- (NSMutableArray *)parseHeader
{
NSString *name = [self parseName];
if (!name)
{
return nil;
}
NSMutableArray *names = [NSMutableArray array];
while (name)
{
[names addObject:name];
if (![self parseSeparator])
{
break;
}
name = [self parseName];
}
return names;
}
//
// parseRecord
//
// Attempts to parse a record from the current scan location. The record
// dictionary will use the fieldNames as keys, or FIELD_X for each column
// X-1 if no fieldName exists for a given column.
//
// returns the parsed record as a dictionary, or nil on failure.
//
- (NSDictionary *)parseRecord
{
//
// Special case: return nil if the line is blank. Without this special case,
// it would parse as a single blank field.
//
if ([self parseLineSeparator] || [scanner isAtEnd])
{
return nil;
}
NSString *field = [self parseField];
if (!field)
{
return nil;
}
NSInteger fieldNamesCount = [fieldNames count];
NSInteger fieldCount = 0;
NSMutableDictionary *record =
[NSMutableDictionary dictionaryWithCapacity:[fieldNames count]];
while (field)
{
NSString *fieldName;
if (fieldNamesCount > fieldCount)
{
fieldName = [fieldNames objectAtIndex:fieldCount];
}
else
{
fieldName = [NSString stringWithFormat:#"FIELD_%d", fieldCount + 1];
[fieldNames addObject:fieldName];
fieldNamesCount++;
}
[record setObject:field forKey:fieldName];
fieldCount++;
if (![self parseSeparator])
{
break;
}
field = [self parseField];
}
return record;
}
//
// parseName
//
// Attempts to parse a name from the current scan location.
//
// returns the name or nil.
//
- (NSString *)parseName
{
return [self parseField];
}
//
// parseField
//
// Attempts to parse a field from the current scan location.
//
// returns the field or nil
//
- (NSString *)parseField
{
NSString *escapedString = [self parseEscaped];
if (escapedString)
{
return escapedString;
}
NSString *nonEscapedString = [self parseNonEscaped];
if (nonEscapedString)
{
return nonEscapedString;
}
//
// Special case: if the current location is immediately
// followed by a separator, then the field is a valid, empty string.
//
NSInteger currentLocation = [scanner scanLocation];
if ([self parseSeparator] || [self parseLineSeparator] || [scanner isAtEnd])
{
[scanner setScanLocation:currentLocation];
return #"";
}
return nil;
}
//
// parseEscaped
//
// Attempts to parse an escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseEscaped
{
if (![self parseDoubleQuote])
{
return nil;
}
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment = [self parseTextData];
if (!fragment)
{
fragment = [self parseSeparator];
if (!fragment)
{
fragment = [self parseLineSeparator];
if (!fragment)
{
if ([self parseTwoDoubleQuotes])
{
fragment = #"\"";
}
else
{
break;
}
}
}
}
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
if (![self parseDoubleQuote])
{
return nil;
}
return accumulatedData;
}
//
// parseNonEscaped
//
// Attempts to parse a non-escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseNonEscaped
{
return [self parseTextData];
}
//
// parseTwoDoubleQuotes
//
// Attempts to parse two double quotes from the current scan location.
//
// returns a string containing two double quotes or nil.
//
- (NSString *)parseTwoDoubleQuotes
{
if ([scanner scanString:#"\"\"" intoString:NULL])
{
return #"\"\"";
}
return nil;
}
//
// parseDoubleQuote
//
// Attempts to parse a double quote from the current scan location.
//
// returns #"\"" or nil.
//
- (NSString *)parseDoubleQuote
{
if ([scanner scanString:#"\"" intoString:NULL])
{
return #"\"";
}
return nil;
}
//
// parseSeparator
//
// Attempts to parse the separator string from the current scan location.
//
// returns the separator string or nil.
//
- (NSString *)parseSeparator
{
if ([scanner scanString:separator intoString:NULL])
{
return separator;
}
return nil;
}
//
// parseLineSeparator
//
// Attempts to parse newline characters from the current scan location.
//
// returns a string containing one or more newline characters or nil.
//
- (NSString *)parseLineSeparator
{
NSString *matchedNewlines = nil;
[scanner
scanCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:&matchedNewlines];
return matchedNewlines;
}
//
// parseTextData
//
// Attempts to parse text data from the current scan location.
//
// returns a non-zero length string or nil.
//
- (NSString *)parseTextData
{
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment;
if ([scanner scanUpToCharactersFromSet:endTextCharacterSet intoString:&fragment])
{
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
//
// If the separator is just a single character (common case) then
// we know we've reached the end of parseable text
//
if (separatorIsSingleChar)
{
break;
}
//
// Otherwise, we need to consider the case where the first character
// of the separator is matched but we don't have the full separator.
//
NSUInteger location = [scanner scanLocation];
NSString *firstCharOfSeparator;
if ([scanner scanString:[separator substringToIndex:1] intoString:&firstCharOfSeparator])
{
if ([scanner scanString:[separator substringFromIndex:1] intoString:NULL])
{
[scanner setScanLocation:location];
break;
}
//
// We have the first char of the separator but not the whole
// separator, so just append the char and continue
//
accumulatedData = [accumulatedData stringByAppendingString:firstCharOfSeparator];
continue;
}
else
{
break;
}
}
if ([accumulatedData length] > 0)
{
return accumulatedData;
}
return nil;
}
#end
Thanks for everyone's assistance. I found the answer on stackoverflow, a 2-3 years old forum (Adding Core Data to existing iPhone project)...
So the issue it seems is that when I first created the project I didn't request using core data and only did that later on. Following the post I posted above:
Add the following to supporting files/projectName-Prefix.pch
#import <CoreData/CoreData.h>
Once I did, the persistenceCoordinator error disappeared...
WOW, big lesson learned there...
-(void)populateDB {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
[....]
}
The Problem might be [self persistentStoreCoordinator]... Do you have a function called "persistentStoreCoordinator" ? If not, you have to write the function.
[EDIT]
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
#synchronized (self)
{
if (__persistentStoreCoordinator != nil)
return __persistentStoreCoordinator;
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myProject" ofType:#"sqlite"];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: #"myProject.sqlite"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath])
{
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
}
Is this Code in another File ? Maybe you forgot to import the .h-file, where persistentStoreCoordinator is declared in.

Remove call history article call one of the data in the database

The database is read-only.
Code:
//获取数据库
FMDatabase * db = [dataManager openDataBase];
//判断数据库是否能打开
if ([db open]) {
//获取通话记录中所有的address
FMResultSet * rs = [db executeQuery:#"SELECT address FROM privacy_call"];
NSString * markaddress = nil;
while ([rs next]) {
NSString * address = [rs stringForColumn:#"address"];
if ([self isEqualContactNumber:address withPrivacyNumber:number] && ![markaddress isEqualToString:address]) {
markaddress = address;
//删除数据
BOOL resultSet = [db executeUpdate:#"DELETE FROM privacy_call WHERE address = ?",address];
success = resultSet;
}
}
[rs close];
}
else {
NSLog(#"数据库打开错误");
return NO;
}
very hard to understand what the question is,
but if i make a guess, this line needs to be updated like this:
//assuming executeUpdate returns a BOOL
BOOL success = [db executeUpdate:[NSString stringWithFormat:#"DELETE FROM privacy_call WHERE address = %#",address]];

Singleton in static library in iOS

I have a static library which contains a Singleton class (FMDB SQLite Data Access), now I open from my main application the connection and do thingss... this works, after that a method in my library want to call the a method on my singleton and I get the error that
-[FMDatabase executeQuery:withArgumentsInArray:]: message sent to deallocated instance 0xa443960
is this not possible what I'm trying to achieve?
this is short version of my singleton
static MySingleton* _sharedMySingleton = nil;
FMDatabase *database;
#ifndef __clang_analyzer__
+(MySingleton*)sharedMySingleton
{
#synchronized([MySingleton class])
{
if (!_sharedMySingleton)
[[self alloc] init];
return _sharedMySingleton;
}
}
#endif
+(id)alloc
{
#synchronized([MySingleton class])
{
NSAssert(_sharedMySingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedMySingleton = [super alloc];
return _sharedMySingleton;
}
}
-(Resource *)getResourceForName:(NSString *)name
{
NSString *select = #"SELECT Data, MimeType FROM File WHERE FileName = ? LIMIT 1";
NSArray *arguments = [NSArray arrayWithObject:[NSString stringWithFormat:#"/%#", name]];
FMResultSet *s = [database executeQuery:select withArgumentsInArray:arguments];
if (s == NULL)
{
FuncFileLog(#"getResourceForName file cant be loaded: %#", [database lastErrorMessage]);
return nil;
}
NSData *data = nil;
NSString *mimeType;
while ([s next])
{
data = [NSData dataFromBase64String:[s stringForColumnIndex:0]];
mimeType = [s stringForColumnIndex:1];
}
Resource *resource = [[[Resource alloc] initWithData:data mimeType:mimeType] autorelease];
return resource;
}
-(BOOL)openDatabase
{
database = [FMDatabase databaseWithPath:[self getDocumentResourcePath]];
return [database open];
}
-(void)closeDatabase
{
[database close];
[database release];
}
-(void)dealloc
{
if (database != NULL)
{
[self closeDatabase];
}
[baseUrl release];
[super dealloc];
}
#end
EDIT:
I found that the dealloc from FMDatabase gets called after my application start return, but dont know why.
EDIT2:
Currently I thought one problem was this line
database = [FMDatabase databaseWithPath:[self getDocumentResourcePath]];
here I have to retain the object.
You don't actually assign the singleton instance:
if (!_sharedMySingleton)
[[self alloc] init];
should be:
if (!_sharedMySingleton)
_sharedMySingleton = [[self alloc] init];
and dump that overridden alloc method.
Also database should be an instance variable in the class, and not at global scope.

Resources