FMDatabaseQueue Error: database is locked - ios

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];

Related

Core Data categories not saving

Trying to import an XML file that has questions, answers, and categories for how-to questions. When I attempt to run this, there are supposed to be 3 categories (and they're visible in the XML file) of LB, OP, and DP. When I run the app, it seems to assign all how-tos to the LB category. What am I missing that's causing all to be assigned to the LB category? Thanks.
howToXMLImporter.h
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
#class howToXMLImporter, HowTos, HowToCategory, HowToCategoryCache;
#protocol howToXMLImporterDelegate <NSObject>
#optional
- (void)importerDidSave:(NSNotification *)saveNotification;
- (void)importerDidFinishParsingData:(howToXMLImporter *)importer;
- (void)importer:(howToXMLImporter *)importer didFailWithError:(NSError *)error;
#end
#interface howToXMLImporter : NSOperation {
#private
id <howToXMLImporterDelegate> __unsafe_unretained delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *xmlConnection;
BOOL done;
BOOL parsingAHowTo;
BOOL storingCharacters;
NSMutableData *characterBuffer;
HowTos *currentHowTo; // Current how-to importer working with
// The number of parsed how-tos is tracked so that the autorelease pool for the parsing thread can be periodically emptied to keep the memory footprint under control.
NSUInteger countForCurrentBatch;
NSManagedObjectContext *insertionContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSEntityDescription *howToEntityDescription;
HowToCategoryCache *theCache;
NSURL *aogURL; // Source of the how-to XML file
}
#property (nonatomic, retain) NSURL *aogURL;
#property (nonatomic, assign) id <howToXMLImporterDelegate> delegate;
#property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, retain, readonly) NSManagedObjectContext *insertionContext;
#property (nonatomic, retain, readonly) NSEntityDescription *howToEntityDescription;
#property (nonatomic, retain, readonly) HowToCategoryCache *theCache;
- (void)main;
#end
howToXMLImporter.m
#import "howToXMLImporter.h"
#import "HowTos.h"
#import "HowToCategory.h"
#import "HowToCategoryCache.h"
#import <libxml/tree.h>
// Function prototypes for SAX callbacks.
static void startElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void endElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void *context, const xmlChar *characters, int length);
static void errorEncounteredSAX(void *context, const char *errorMessage, ...);
// Forward reference. The structure is defined in full at the end of the file.
static xmlSAXHandler simpleSAXHandlerStruct;
// Class extension for private properties and methods.
#interface howToXMLImporter ()
#property BOOL storingCharacters;
#property (nonatomic, retain) NSMutableData *characterBuffer;
#property BOOL done;
#property BOOL parsingAHowTo;
#property NSUInteger countForCurrentBatch;
#property (nonatomic, retain) HowTos *currentHowTo;
#property (nonatomic, retain) NSURLConnection *xmlConnection;
#property (nonatomic, retain) NSDateFormatter *dateFormatter;
#end
static double lookuptime = 0;
#implementation howToXMLImporter
// Pull in these variables
#synthesize aogURL, delegate, persistentStoreCoordinator;
#synthesize xmlConnection, done, parsingAHowTo, storingCharacters, currentHowTo, countForCurrentBatch, characterBuffer;
- (void)main {
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] addObserver:delegate selector:#selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
done = NO;
self.characterBuffer = [NSMutableData data];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:aogURL];
// create the connection with the request and start loading the data
xmlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed to the context for streaming parsing.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, (__bridge void *)(self), NULL, 0, NULL);
if (xmlConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.dateFormatter = nil;
self.xmlConnection = nil;
self.currentHowTo = nil;
theCache = nil;
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in import thread: %#", [saveError localizedDescription]);
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] removeObserver:delegate name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importerDidFinishParsingData:)]) {
[self.delegate importerDidFinishParsingData:self];
}
}
- (NSManagedObjectContext *)insertionContext {
if (insertionContext == nil) {
insertionContext = [[NSManagedObjectContext alloc] init];
[insertionContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return insertionContext;
}
- (void)forwardError:(NSError *)error {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importer:didFailWithError:)]) {
[self.delegate importer:self didFailWithError:error];
}
}
- (NSEntityDescription *)howToEntityDescription {
if (howToEntityDescription == nil) {
howToEntityDescription = [NSEntityDescription entityForName:#"HowTo" inManagedObjectContext:self.insertionContext];
}
return howToEntityDescription;
}
- (HowToCategoryCache *)theCache {
if (theCache == nil) {
theCache = [[HowToCategoryCache alloc] init];
theCache.managedObjectContext = self.insertionContext;
}
return theCache;
}
- (HowTos *)currentHowTo {
if (currentHowTo == nil) {
currentHowTo = [[HowTos alloc] initWithEntity:self.howToEntityDescription insertIntoManagedObjectContext:self.insertionContext];
}
return currentHowTo;
}
// Forward errors to the delegate.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self performSelectorOnMainThread:#selector(forwardError:) withObject:error waitUntilDone:NO];
done = YES; // Ends the run loop.
}
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Process downloaded chunk of data.
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Signal the context that parsing is complete by passing "1" as the last parameter.
xmlParseChunk(context, NULL, 0, 1);
context = NULL;
done = YES; // Ends the run loop.
}
static const NSUInteger kImportBatchSize = 20;
- (void)finishedCurrentHowTo {
parsingAHowTo = NO;
self.currentHowTo = nil;
countForCurrentBatch++;
if (countForCurrentBatch == kImportBatchSize) {
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in how-to import thread: %#", [saveError localizedDescription]);
countForCurrentBatch = 0;
}
}
// Character data is appended to a buffer until the current element ends.
- (void)appendCharacters:(const char *)charactersFound length:(NSInteger)length {
[characterBuffer appendBytes:charactersFound length:length];
}
- (NSString *)currentString {
// Create a string with the character data using UTF-8 encoding
NSString *currentString = [[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding];
[characterBuffer setLength:0];
return currentString;
}
#end
// XML element names and their string lengths for parsing comparison; include null terminator
static const char *kName_HowTos = "How_Tos"; // Container tag for product
static const NSUInteger kLength_HowTos = 8;
static const char *kName_Question = "how-to_question"; // Didn't include how-to_id for now
static const NSUInteger kLength_Question = 16;
static const char *kName_Answer = "how-to_answer";
static const NSUInteger kLength_Answer = 14;
static const char *kName_Category = "how-to_category";
static const NSUInteger kLength_Category = 16;
static void startElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// The second parameter to strncmp is the name of the element, which we know from the XML schema of the feed. The third parameter to strncmp is the number of characters in the element name, plus 1 for the null terminator.
if (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
importer.parsingAHowTo = YES;
// May want to set default variable values here
} else if (importer.parsingAHowTo &&
(
(!strncmp((const char *)localname, kName_Question, kLength_Question) ||
!strncmp((const char *)localname, kName_Answer, kLength_Answer) ||
!strncmp((const char *)localname, kName_Category, kLength_Category)
)
)
) {
importer.storingCharacters = YES;
}
}
// This callback is invoked when the parser reaches the end of a node
static void endElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
if (importer.parsingAHowTo == NO) return;
if (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
[importer finishedCurrentHowTo];
} else if (!strncmp((const char *)localname, kName_Question, kLength_Question)) {
importer.currentHowTo.question = importer.currentString;
NSLog(#"Question: %#",importer.currentHowTo.question);
} else if (!strncmp((const char *)localname, kName_Answer, kLength_Answer)) {
importer.currentHowTo.answer = importer.currentString;
NSLog(#"Answer: %#",importer.currentHowTo.answer);
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
double before = [NSDate timeIntervalSinceReferenceDate];
HowToCategory *category = [importer.theCache howToCategoryWithName:importer.currentString];
double delta = [NSDate timeIntervalSinceReferenceDate] - before;
lookuptime += delta;
importer.currentHowTo.category = category;
NSLog(#"Category: %#",importer.currentHowTo.category.name);
}
importer.storingCharacters = NO;
}
// This callback is invoked when the parser encounters character data inside a node. Importer class determines how to use the character data.
static void charactersFoundSAX(void *parsingContext, const xmlChar *characterArray, int numberOfCharacters) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// A state variable, "storingCharacters", is set when nodes of interest begin and end. Determines whether character data is handled or ignored.
if (importer.storingCharacters == NO) return;
[importer appendCharacters:(const char *)characterArray length:numberOfCharacters];
}
static void errorEncounteredSAX(void *parsingContext, const char *errorMessage, ...) {
// Handle errors as appropriate
NSCAssert(NO, #"Unhandled error encountered during SAX parse.");
}
HowToCategoryCache.h
#import <Foundation/Foundation.h>
#class HowToCategory;
#interface HowToCategoryCache : NSObject {
NSManagedObjectContext *managedObjectContext;
NSUInteger cacheSize; // Number of objects that can be cached
NSMutableDictionary *cache; // A dictionary holds the actual cached items
NSEntityDescription *howToCategoryEntityDescription;
NSPredicate *howToCategoryNamePredicateTemplate;
NSUInteger accessCounter; // Counter used to determine the least recently touched item.
// Some basic metrics are tracked to help determine the optimal cache size for the problem.
CGFloat totalCacheHitCost;
CGFloat totalCacheMissCost;
NSUInteger cacheHitCount;
NSUInteger cacheMissCount;
}
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property NSUInteger cacheSize;
#property (nonatomic, strong) NSMutableDictionary *cache;
#property (nonatomic, strong, readonly) NSEntityDescription *howToCategoryEntityDescription;
#property (nonatomic, strong, readonly) NSPredicate *howToCategoryNamePredicateTemplate;
- (HowToCategory *)howToCategoryWithName:(NSString *)name;
#end
HowToCategoryCache.m
#import "HowToCategoryCache.h"
#import "HowToCategory.h"
// CacheNode is a simple object to help with tracking cached items
#interface HowToCacheNode : NSObject {
NSManagedObjectID *objectID;
NSUInteger accessCounter;
}
#property (nonatomic, strong) NSManagedObjectID *objectID;
#property NSUInteger accessCounter;
#end
#implementation HowToCacheNode
#synthesize objectID, accessCounter;
#end
#implementation HowToCategoryCache
#synthesize managedObjectContext, cacheSize, cache;
- (id)init {
self = [super init];
if (self != nil) {
cacheSize = 15;
accessCounter = 0;
cache = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (cacheHitCount > 0) NSLog(#"average cache hit cost: %f", totalCacheHitCost/cacheHitCount);
if (cacheMissCount > 0) NSLog(#"average cache miss cost: %f", totalCacheMissCost/cacheMissCount);
howToCategoryEntityDescription = nil;
howToCategoryNamePredicateTemplate = nil;
}
// Implement the "set" accessor rather than depending on #synthesize so that we can set up registration for context save notifications.
- (void)setManagedObjectContext:(NSManagedObjectContext *)aContext {
if (managedObjectContext) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
managedObjectContext = aContext;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
// When a managed object is first created, it has a temporary managed object ID. When the managed object context in which it was created is saved, the temporary ID is replaced with a permanent ID. The temporary IDs can no longer be used to retrieve valid managed objects. The cache handles the save notification by iterating through its cache nodes and removing any nodes with temporary IDs.
- (void)managedObjectContextDidSave:(NSNotification *)notification {
HowToCacheNode *cacheNode = nil;
NSMutableArray *keys = [NSMutableArray array];
for (NSString *key in cache) {
cacheNode = [cache objectForKey:key];
if ([cacheNode.objectID isTemporaryID]) {
[keys addObject:key];
}
}
[cache removeObjectsForKeys:keys];
}
- (NSEntityDescription *)howToCategoryEntityDescription {
if (howToCategoryEntityDescription == nil) {
howToCategoryEntityDescription = [NSEntityDescription entityForName:#"HowToCategory" inManagedObjectContext:managedObjectContext];
}
return howToCategoryEntityDescription;
}
static NSString * const kCategoryNameSubstitutionVariable = #"NAME";
- (NSPredicate *)categoryNamePredicateTemplate {
if (howToCategoryNamePredicateTemplate == nil) {
NSExpression *leftHand = [NSExpression expressionForKeyPath:#"name"];
NSExpression *rightHand = [NSExpression expressionForVariable:kCategoryNameSubstitutionVariable];
howToCategoryNamePredicateTemplate = [[NSComparisonPredicate alloc] initWithLeftExpression:leftHand rightExpression:rightHand modifier:NSDirectPredicateModifier type:NSLikePredicateOperatorType options:0];
}
return howToCategoryNamePredicateTemplate;
}
#define USE_CACHING // Undefine this macro to compare performance without caching
- (HowToCategory *)howToCategoryWithName:(NSString *)name {
NSTimeInterval before = [NSDate timeIntervalSinceReferenceDate];
#ifdef USE_CACHING
// check cache
HowToCacheNode *cacheNode = [cache objectForKey:name];
if (cacheNode != nil) {
cacheNode.accessCounter = accessCounter++; // cache hit, update access counter
HowToCategory *category = (HowToCategory *)[self.managedObjectContext objectWithID:cacheNode.objectID];
totalCacheHitCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheHitCount++;
return category;
}
#endif
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // cache missed, fetch from store - if not found in store there is no category object for the name and we must create one
[fetchRequest setEntity:self.howToCategoryEntityDescription];
NSLog(#"Fetch Request: %#",fetchRequest);
NSPredicate *predicate = [self.howToCategoryNamePredicateTemplate predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:name forKey:kCategoryNameSubstitutionVariable]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSAssert1(fetchResults != nil, #"Unhandled error executing fetch request in import thread: %#", [error localizedDescription]);
HowToCategory *category = nil;
if ([fetchResults count] > 0) {
category = [fetchResults objectAtIndex:0]; // get category from fetch
} else if ([fetchResults count] == 0) {
// category not in store, must create a new category object
category = [[HowToCategory alloc] initWithEntity:self.howToCategoryEntityDescription insertIntoManagedObjectContext:managedObjectContext];
category.name = name; //name
// Set the sort order for the category
if ([category.name isEqualToString:#"LB"]) {
category.order = [NSNumber numberWithInt:1];
} else if ([category.name isEqualToString:#"DP"]) {
category.order = [NSNumber numberWithInt:2];
} else if ([category.name isEqualToString:#"OP"]) {
category.order = [NSNumber numberWithInt:3];
}
}
#ifdef USE_CACHING
// add to cache
// first check to see if cache is full
if ([cache count] >= cacheSize) {
// evict least recently used (LRU) item from cache
NSUInteger oldestAccessCount = UINT_MAX;
NSString *key = nil, *keyOfOldestCacheNode = nil;
for (key in cache) {
HowToCacheNode *tmpNode = [cache objectForKey:key];
if (tmpNode.accessCounter < oldestAccessCount) {
oldestAccessCount = tmpNode.accessCounter;
keyOfOldestCacheNode = key;
}
}
// retain the cache node for reuse
cacheNode = [cache objectForKey:keyOfOldestCacheNode];
// remove from the cache
if (keyOfOldestCacheNode != nil)
[cache removeObjectForKey:keyOfOldestCacheNode];
} else {
// create a new cache node
cacheNode = [[HowToCacheNode alloc] init];
}
cacheNode.objectID = [category objectID];
cacheNode.accessCounter = accessCounter++;
[cache setObject:cacheNode forKey:name];
#endif
totalCacheMissCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheMissCount++;
return category;
}
#end

Sort UITableView by distance

I am trying to sort my tableview in ascending order by distance that I calculate from coordinates. Everything works like a charm except I can't get it in ascending order, I have been mucking around with NSSortDescriptor etc., but getting unlucky, any help would be appreciated, here is my code:
- (void) retrieveData
{
NSURL *url = [NSURL URLWithString:jsonFile];
NSData *data = [NSData dataWithContentsOfURL:url];
_jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
_salesArray = [[NSMutableArray alloc]init];
for (int i = 0; i < _jsonArray.count; i++) {
NSString *sID = [[_jsonArray objectAtIndex:i] objectForKey:#"id"];
NSString *sName = [[_jsonArray objectAtIndex:i] objectForKey:#"name"];
NSString *sAddress = [[_jsonArray objectAtIndex:i] objectForKey:#"address"];
NSString *sPostcode = [[_jsonArray objectAtIndex:i] objectForKey:#"postcode"];
__block NSString *distance;
CLGeocoder *geocoder = [[CLGeocoder alloc]init];
[geocoder geocodeAddressString:sPostcode completionHandler:^(NSArray *placemarks, NSError *error) {
if (error == nil && placemarks.count > 0) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocation *myLocation = self.manager.location;
CLLocationDistance miles = [location distanceFromLocation:myLocation];
//this is the variable i want in my convenience init.
distance = [NSString stringWithFormat:#"%.1f m", (miles/1609.344)];
}
}];
[_salesArray addObject:[[sales alloc] initWithSales:sID andName:sName andAddress:sAddress andPostcode:distance]];
}
[_salesArray sortUsingComparator:
^NSComparisonResult(id obj1, id obj2){
sales *p1 = (sales *)obj1;
sales *p2 = (sales *)obj2;
if (p1.postcode > p2.postcode) {
return (NSComparisonResult)NSOrderedDescending;
}
if (p1.postcode < p2.postcode) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}
];
[self.tableView reloadData];
}
There are a few of issues here:
The geocodeAddressString imposes a few limitations, as outlined in the documentation:
This method submits the specified location data to the geocoding server asynchronously and returns. Your completion handler block will be executed on the main thread. After initiating a forward-geocoding request, do not attempt to initiate another forward- or reverse-geocoding request.
Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value kCLErrorNetwork to your completion handler.
Several key observations here:
This runs asynchronously (so you cannot call geocodeAddressString and use its results immediately afterwards). You have do invoke the work contingent on the geocoding inside the completion block.
You should not be starting the next geocode request until the prior one completes.
This means that you have to geocode the first postal code, let it complete asynchronously (i.e. later), geocode the next one, let it complete, etc., and only then do your sort and reload the table. A simple for loop is not an appropriate way to do this. You can either write a method that does a single geocode and invokes the next geocode in the completion block, or you can use NSOperation subclass as I have below.
I would advise storing the distance as a NSNumber. In MVC, the one decimal place string representation is a "view" behavior, and should probably not be part of the "model".
The advantage of this is that when you want to sort the objects, you can simply invoke the compare method for the NSNumber. For example, if salesPersonnel was a NSMutableArray of objects which each SalesPerson object has the NSNumber property called distance, you could then do:
[self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) {
return [obj1.distance compare:obj2.distance];
}];
I wasn't sure if your sales entries per actual sales transactions or sales personnel, so I apologize if I misinterpreted the object types, but hopefully this illustrates the idea.
You can do this any way you want, but for me, when I want to run a number of asynchronous tasks, but do so sequentially, I gravitate to concurrent NSOperation subclass which I'll add to a serial NSOperationQueue.
NSError *error;
NSArray *addressEntries = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSAssert(addressEntries, #"unable to parse: %#", error);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
self.salesPersonnel = [NSMutableArray array];
// define sort operation that will be called when all of the geocode attempts are done
NSOperation *sortAndReloadTableOperation = [NSBlockOperation blockOperationWithBlock:^{
[self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) {
return [obj1.distance compare:obj2.distance];
}];
[self.tableView reloadData];
}];
// create the geocode operations
for (NSDictionary *addressEntry in addressEntries) {
SalesPerson *salesPerson = [[SalesPerson alloc] initWithSalesId:addressEntry[#"id"]
name:addressEntry[#"name"]
address:addressEntry[#"address"]
postalCode:addressEntry[#"postcode"]];
[self.salesPersonnel addObject:salesPerson];
NSOperation *geocodeOperation = [[GeocodeOperation alloc] initWithPostalCode:salesPerson.postalCode completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks firstObject];
CLLocation *location = placemark.location;
CLLocationDistance meters = [location distanceFromLocation:self.currentLocation];
salesPerson.distance = #(meters / 1609.344);
}];
[sortAndReloadTableOperation addDependency:geocodeOperation]; // note, the final sort is dependent upon this finishing
[queue addOperation:geocodeOperation]; // go ahead and queue up the operation
}
// now we can queue the sort and reload operation, which won't start until the geocode operations are done
[[NSOperationQueue mainQueue] addOperation:sortAndReloadTableOperation];
And the GeocodeOperation is a basic concurrent NSOperation subclass:
// GeocodeOperation.h
#import <Foundation/Foundation.h>
typedef void(^GeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
#interface GeocodeOperation : NSOperation
#property (nonatomic, copy) GeocodeCompletionHandler geocodeCompletionHandler;
- (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler;
#end
and the implementation (note, the main method is the only interesting bit here ... all the rest is routine concurrent NSOperation subclass code; personally, I move all of the concurrent NSOperation stuff into a base class, which cleans up this GeocodeOperation code, but I didn't want to confuse this further, so I've kept this simple):
// GeocodeOperation.m
#import "GeocodeOperation.h"
#import CoreLocation;
#interface GeocodeOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, copy) NSString *postalCode;
#end
#implementation GeocodeOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (CLGeocoder *)sharedGeocoder
{
static CLGeocoder *geocoder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
geocoder = [[CLGeocoder alloc]init];
});
return geocoder;
}
- (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler
{
self = [super init];
if (self) {
_postalCode = [postalCode copy];
_geocodeCompletionHandler = geocodeCompletionHandler;
}
return self;
}
- (void)main
{
[[self sharedGeocoder] geocodeAddressString:self.postalCode completionHandler:^(NSArray *placemarks, NSError *error) {
if (self.geocodeCompletionHandler) {
self.geocodeCompletionHandler(placemarks, error);
}
[self completeOperation];
}];
}
#pragma mark - NSOperation methods
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (_executing != executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (_finished != finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
#end
I think the problem is postcode is an NSString. So in your block (p1.postcode > p2.postcode) is comparing the ADDRESS LOCATIONS, not the string values themselves.
You want to use the NSString function compare: instead of doing it yourself.
Try this:
[_salesArray sortUsingComparator:
^NSComparisonResult(id obj1, id obj2){
sales *p1 = (sales *)obj1;
sales *p2 = (sales *)obj2;
NSString *postcode1 = p1.postcode;
NSString *postcode2 = p2.postcode;
return [postcode1 compare:posecode2];
];

Sqlite Database is locked error 5 when used insert or update query?

I have create database class which have all operation CRUD. i have read successfully all database whenever once time insert or update query fire perfectly done after that insert or update query won't work but it's read all data sucessfully.
I have already used singleton but it's not work.
-(MyClass *)sharedInstance
{
static MyClass *sharedInstance=nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
because of multi-thread issue but it's not work.
My insert function
- (NSNumber *) insertRow:(NSDictionary *) record
{
int dictionarySize = [record count];
NSMutableData *dKeys = [NSMutableData dataWithLength:sizeof(id) *dictionarySize];
NSMutableData * dValues = [NSMutableData dataWithLength: sizeof(id) * dictionarySize];
[record getObjects:(__unsafe_unretained id *)dValues.mutableBytes andKeys:(__unsafe_unretained id *)dKeys.mutableBytes];
NSMutableArray *placeHoldersArray = [NSMutableArray arrayWithCapacity:dictionarySize];
for (int count = 0 ; count < dictionarySize ; count++)
[placeHoldersArray addObject:#"?"];
NSString *query = [NSString stringWithFormat:#"insert into %# (%#) values (%#)",tableName,[[record allKeys]componentsJoinedByString:#","],[placeHoldersArray componentsJoinedByString:#","]];
[self bindSQL:[query UTF8String] withVargs:(va_list)dValues.mutableBytes];
sqlite3_step(statment);
if(sqlite3_finalize(statment) == SQLITE_OK)
{
return [self lastInsertId];
}
else
{
NSLog(#"doQuery: sqlite3_finalize failed (%s)", sqlite3_errmsg(database));
return #0;
}
}

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

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.

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