Updates
19th August 2015 - Bug seems to have now been fixed in the 3.13 update, even though the only thing they list in their changelog is "Fixed an issue which caused crashes when using setCampaignParametersFromUrl". Take that as you will.
8th June 2015 - Still encountering this problem. If I disable automatic sending of events ([GAI sharedInstance].dispatchInterval = -1;) then I still receive errors. I assume, therefore, that the problem lies with inserting the event into the Google Analytics SQLite database, that somehow my own database statement that is currently in progress are becoming void.
10th June 2015 - Still encountering crashes. Tried removing my controllers extending GAITrackedViewController and sending the createScreenView track manually with no change in crash frequency.
25th June 2015 - Still encountering crashes.
Intro
I have added the Google Analytics SDK 3.12 to my iPhone app and everything is working as expected - I run the app and can see all of the hits and events that I have setup coming through on the Web interface.
I am initialising the SDK in my AppDelegate right at the top of my didFinishLaunchingWithOptions, like so:
[[GAI sharedInstance] trackerWithTrackingId:GOOGLE_ANALYTICS_ID];
The problem
However, I have found that running Google Analytics creates errors when I try and use SQLite for myself. They can manifest as serious errors such as:
"Database disk image is malformed" and then insta-crashes
"Disc i/O error" whenever I run a query (though doesn't crash)
And they can also cause my own SQLite queries to just fail, for instance:
if (! sqlite3_prepare_v2(_db, [sql UTF8String], -1, &_statement, NULL) == SQLITE_OK) {`
// ..
// ..
if (sqlite3_step(_statement) == SQLITE_ROW) {
Will result, randomly, in the following error:
sqlite3_prepare_v2 EXC_BAD_ACCESS (code=1, address=0x6800000000)
If I comment out the SDK initialisation then everything goes back to being incredibly stable. Uncomment it again and it will crash the app within a minute.
Pre-emptive question answering
Am running this on a iPhone 6 running 8.3 (12F70).
Have tried uninstalling and reinstalling the app.
I have added all of the pre-requisites for Google Analytics to work; all of the .m files to the library, the libGoogleAnalyticsServices.a file, and also the Linked Frameworks and Libraries.
I also have Crashlytics, but have tried commenting it out from the code ([Fabric with:#[CrashlyticsKit]];) and removing its library from the Linked Frameworks and Libraries with exactly the same results.
Code
Setting up the class
// In didFinishLaunchingWithOptions
[Db setup];
[Db connect];
Accesses the class
Db * db = [[Db alloc] init];
if ([db prepare:#"SELECT * FROM `table` WHERE `id` = ?" withBindings:#[#"123"]]) {
while ([db stepThrough]) {
// ..
}
}
[db finalise];
The class
(Have indicated where the errors appear with comments)
#implementation Db
static sqlite3 * _db;
static NSString * _dbPath;
#pragma mark - Setup
+ (BOOL)setup {
NSString * sqlBundlePath = [[NSBundle mainBundle] pathForResource:#"db" ofType:#"sqlite"];
NSString * documentsFolder = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString * sqlDocumentPath = [[documentsFolder stringByAppendingPathComponent:#"db"] stringByAppendingPathExtension:#"sqlite"];
NSFileManager * fileManager = [NSFileManager defaultManager];
if (! [fileManager fileExistsAtPath:sqlDocumentPath]) {
NSError * error;
BOOL success = [fileManager copyItemAtPath:sqlBundlePath toPath:sqlDocumentPath error:&error];
if (! success) {
return NO;
}
}
_dbPath = sqlDocumentPath;
return YES;
}
+ (BOOL)connect {
sqlite3_config(SQLITE_CONFIG_SERIALIZED);
return sqlite3_open([_dbPath UTF8String], &_db);
}
#pragma mark - Querying
- (BOOL)prepare:(NSString *)sql withBindings:(NSArray *)bindings {
// ERROR CAN OCCUR ON THE FOLLOWING LINE
if (! sqlite3_prepare_v2(_db, [sql UTF8String], -1, &_statement, NULL) == SQLITE_OK) {
NSLog(#"Error whilst preparing query: %s", sqlite3_errmsg(_db));
sqlite3_finalize(_statement);
return NO;
}
for (int i = 0; i < [bindings count]; i++) {
sqlite3_bind_text(_statement,
i + 1,
[bindings[i] isKindOfClass:[NSNull class]] ? [#"" UTF8String] : [bindings[i] UTF8String],
-1,
SQLITE_TRANSIENT);
}
return YES;
}
- (BOOL)stepThrough {
// ERROR CAN OCCUR ON THE FOLLOWING LINE
if (sqlite3_step(_statement) == SQLITE_ROW) {
return YES;
}
sqlite3_finalize(_statement);
return NO;
}
- (void)finalise {
sqlite3_finalize(_statement);
}
#end
Upgrading to the new version of the SDK (3.13) fixed this issue (at least for me), even though their changelog does not specifically mention it.
Related
Some background for this issue, I'm trying to include what I think may be relevant to help understand the context.
I am currently adding an linked library which used Core Data to save some user information and a feature which adds an Entity to the pre-existing Core Data model already in the app. Each managedObjectContext has its own instance when created (verified) as well as its own PSC and MOM and neither interact with the other's entities(thus seem to be independent).
The entirety of the following code, errors, and (I believe issue) is in the Main Target of the app. (Hopefully) not the newly added linked library.
The saveContext method is:
- (void)saveContext {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
// Register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myManagedObjectContextDidSaveNotificationHandler:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
if (self.managedObjectContext != nil) {
if ([self.managedObjectContext hasChanges]) {
BOOL success = [self.managedObjectContext save:&error];
if (!success) {
[Error showErrorByAppendingString:NSLocalizedString(#"UnableToSaveChanges", nil) withError:error];
} else {
//
}
}
}
// Unregister
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
});
}
When called, error = nil, success = NO and by forcing the compiler past the exception I get the following:
CoreData: error: exception during obtainPermenantIDsForObjects: Updating max pk failed: attempt to write a readonly database with userInfo of { NSSQLiteErrorDomain = 1032;
}
I have googled, "NSSQLiteErrorDomain = 1032", "obtainPermenantIDsForObjects", and "CoreData readonly database". It does appear that the key primary key for each object is the same, but I am setting that value, I believe sqlite is. I have not found any solutions to help with this. I do have the argument passed on launch, "Concurrency Debug 1" set to on.
I have not implemented obtainPermenantIDsForObjects and I've searched the whole project and cant find its implementation so I think CoreData is using this.
The saveContext method is called on the main queue because thats how my predecessors rolled out the code and I don't have time at the moment to deal with it.
The method calling saveContext (from a background thread):
- (NSMutableArray *)convertRawStepDataTo:(NSMutableArray*)steps
withDates:(NSMutableArray*)dates
inManagedObjectContext:(NSManagedObjectContext*)theMOC {
NSMutableArray *theStepsArray = [[NSMutableArray alloc] init];
// prepare values for chart
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
StepSelector *theSelector = [[StepSelector alloc] init];
NSString* apiSelectionForStep = [theSelector getCurrentSelectionString];
for (int iter = 0; iter < steps.count; iter++) {
NSNumber *currStepValue = [steps objectAtIndex:iter];
// NSNumber *stepCountforIter = [NSNumber numberWithLong:[[steps objectAtIndex:iter] longValue]];
NSNumber* dateForIter = [NSNumber numberWithLong:[[dates objectAtIndex:iter] longLongValue]];
Step *step = [delegate addStepObjectToPersistentStorewithAPI:apiSelectionForStep
andStep:stepCountforIter
andDate:dateForIter
forMOC:theMOC];
[theStepsArray addObject:step];
if (VERBOSE) {
NSLog(#"This is step number %d, with object ID: %#", count, [theMOC objectWithID:step.objectID]);
count++;
}
}
[delegate saveContext];
return theStepsArray;
}
Thats all I can think that might help. The source for the MOC in the main target is the appDelegate which is where all the core data code was written initially.
EDIT Here is the requested PSC code. The store is located in the documents directory. I discovered that these objects are being saved to the Persistent Store.. but the error is still occurs. Se below for PSC code:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [self getStoreURL];
// Rollback journalling mode...
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption,
NSFileProtectionComplete, NSFileProtectionKey,
#{#"journal_mode": #"TRUNCATE"}, NSSQLitePragmasOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error = nil;
self.persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error];
if (!self.persistentStore) {
NSLog(#"Error: %#",error);
[Error showErrorByAppendingString:NSLocalizedString(#"UnableToFindDatabaseFile", nil) withError:error];
}
return persistentStoreCoordinator;
}
-(NSURL *)getStoreURL {
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kSQLFILENAME];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:SQLFILEPATHRESOURCE ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
return storeUrl;
}
The NSSQLiteErrorDomain key means that this error came from SQLite, and that Core Data is passing it back to you. SQLite defines error 1032 as follows:
The SQLITE_READONLY_DBMOVED error code is an extended error code for SQLITE_READONLY. The SQLITE_READONLY_DBMOVED error code indicates that a database cannot be modified because the database file has been moved since it was opened, and so any attempt to modify the database might result in database corruption if the processes crashes because the rollback journal would not be correctly named.
...which appears to mean that SQLite is making the persistent store file read only because something has happened to it since it was opened, and SQLite is trying to prevent data corruption.
I don't see anything in the code you've posted that is obviously at fault, at least as far as the error code description goes. So I wonder, are you doing anything anywhere else that would directly affect the persistent store file (i.e. touching the file in any way at all instead of going through Core Data fetch/save calls)?
The mention of the rollback journal in the error code description makes me wonder if setting journal_mode to TRUNCATE is related. If it were me, I'd remove that (I don't know what it's intended to accomplish here) or set it to DELETE. At least for testing purposes, anyway, in the hope of understanding the problem better.
I'm using Xcode 5.02 and iOS 7.04 and I've been searching long and hard to solve this annoying bug, and after many hours of debugging, I still cannot squash this bug.
So I'm using a UIManagedDocument Helper class in order to retrieve my data
+ (void)openDocument:(NSArray *)documentData {
NSString *documentName = documentData[0];
CompletionBlock completionBlock = documentData[1];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
completionBlock(document);
preparingDocument = NO;
};
if(!preparingDocument){
preparingDocument = YES;
if(!([fileManager fileExistsAtPath:[url path]])){
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForCreating
completionHandler:OnDocumentDidLoad];
} else if(document.documentState == UIDocumentStateClosed){
[document openWithCompletionHandler:OnDocumentDidLoad];
} else if (document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
} else {
//Try till Document is Ready
[self performSelector:#selector(openDocument:)
withObject:documentData
afterDelay:0.5];
}
}
In my view controller, I use this helper class in order to gain access to my ManagedObjectContext
- (void)updateContext{
[DocumentHelper openDocument:#[DOCUMENT_NAME, ^(UIManagedDocument *document) {
self.managedObjectContext = document.managedObjectContext;
}]];
}
And this updateContext method gets called usually upon updating the CoreData, such as adding or deleting new items, however this method is also called in the (void)viewWillAppear method and in a notification block when the Application is in the Foreground (Using the Application Delegate)
Whenever I put the application into the background and reopen the application, the application crashes saying
*** -[UIManagedDocument _setInConflict:]: message sent to deallocated instance 0x1701b0ae0
I used malloc and the NSZombie Profile manager, but no matter what this bug is like a ticking time bomb. The error occurs upon a random number of times of closing and reopening the app.
I experienced the same problem today.
* -[UIManagedDocument _setInConflict:]: message sent to deallocated instance 0x1701b0ae0
This message indicates that your UIManagedDocument instance has been deallocated but is having a message sent to it. I solved the issue in my project by declaring the document variable as a file-level variable (outside of the method) so that it would not be too-hastily released, and only setting it to nil after I was done using it.
EDIT to answer question:
My app checks and updates from an iCloud document in the app delegate. In my AppDelegate.h file, I have this:
#interface CSPAppDelegate : UIResponder <UIApplicationDelegate> {
BOOL iCloudAvailable;
NSMetadataQuery *_query;
CSPICloudDocument *doc; // <<< declare document variable here instead of in method
}
The document is instantiated in the relevant method. The only real difference between your code and what I'm doing is where I've declared the variable. This was sufficient to solve the same error for me.
I'm trying to follow this tutorial: http://sqlcipher.net/ios-tutorial/
I create a database called "sqlcipher.db" then I recreate this
When I execute this code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent: #"encrypted.db"];
sqlite3 *db;
if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) {
const char* key = [#"secret" UTF8String];
int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
sqlite3_key(db, key, strlen(key));
if (sqlite3_exec(db, (const char*) "SELECT count(*) FROM t1;", NULL, NULL, NULL) == SQLITE_OK) {
// password is correct, or, database has been initialized
NSLog(#"Hello 1");
} else {
// incorrect password!
NSLog(#"Hello 2");
}
sqlite3_close(db);
} else {
NSLog(#"Hello 3");
}
}
It allways outs "Hello 2".
When I try to reproduce the steps to crate an encrypted db described here http://zetetic.net/blog/2009/12/29/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher.html#disqus_thread I can't get it encrypted, I believe that it is beacause I am using sqlite3 mac command.
So I saw in the comments that S Lombardo says that I have to compile a command line sqlcipher executable but the link doesn't works.
How should I encrypt my database to use it with SQLcipher?
Did anyone have success using sqlicipher in iOS?
After one hour Googling i've found how to compile sqlcipher command line for OSX:
I hope this could help somebody.
https://groups.google.com/forum/#!msg/sqlcipher/bd1R13RpZHQ/SEPK8YrRt1gJ
Clearly I must not be doing something right but this happens on random occasions. I can't make the issue appear by following certain steps so it has become extremely hard to debug. I have an app in which every time an object is added, or deleted it writes the file to a plist. I can verify the plist in the simulator and I also have an NSLog each time my save function is called. The data is loaded when applicationWillEnterForeground is called, which is also working correctly. On certain occasions after starting up the app it will revert to the previous save before the most recent changes. Does iOS cache data files? If it tries to load a file from disk to an array, could it possible already have the previous load in a cache and create the array with that data instead?
Save method:
- (void)saveFile {
// saves data to file
NSLog(#"save file reached");
#ifdef LITE_VERSION
[self.aquariums writeToFile:[self dataFilePathLite] atomically:YES];
#else
[self.aquariums writeToFile:[self dataFilePath] atomically:YES];
#endif
}
Load method; I just added the if (self.aquariums == null) check and it might have solved the issue but it's hard for me to confirm since I can't recreate the problem:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
NSLog(#"applicationDidBecomeActive called");
if (self.aquariums == NULL) {
NSLog(#"aquariums null, loading file");
#ifdef LITE_VERSION
NSString *filePath = [self dataFilePathLite];
#else
NSString *filePath = [self dataFilePath];
#endif
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
self.aquariums = array;
[array release];
[self update21];
} else {
#ifdef LITE_VERSION
[self convertFileName];
#else
[self update20];
#endif
}
application.applicationIconBadgeNumber = 0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
aquaPlannerAppDelegate_iPad *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.rootView viewDidAppear:YES];
}
}
}
After releasing and not getting any reports of loss of data the issue must definitely have been
if (self.aquariums == NULL) {
so people remember if you are going to reload data when an app returns from being in the background set it to null first and release
aquariums = NULL;
[aquariums release];
I spent hours trying to fix that but I've just given up; I have no idea what's wrong.
In my app, I do nested SQL operations to set all my objects correctly. For some reason, sometimes the sqlite3 objects do not get release properly, causing the memory to go up the roof. I understand it is a problem with using correctly sql3_close and sql3_finalize. However, as you will see, I think I have used them correctly.
Here is the source of the problem:
- (NSArray *) getAllSessions {
if (sqlite3_open(dbPath, &db) == SQLITE_OK) {
if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
//I found out that doing something like that
//toAdd.in_loc = [self getIndoorLocationWithId:[NSNumber numberWithInt:(int)sqlite3_column_int(statement, 6)]];
//messes the memory up all the time
//but doing that works OK:
NSNumber *_id = [[NSNumber alloc] initWithInt:(int) sqlite3_column_int(statement, 5)];
toAdd.out_loc = [self getOutdoorLocationWithId:_id];
[_id release];
//So I did the same with the second one, but this one messes memory up:
NSNumber *id2 = [[NSNumber alloc] initWithInt:(int)sqlite3_column_int(statement, 6)];
toAdd.in_loc = [self getIndoorLocationWithId:id2];
[id2 release];
}
sqlite3_finalize(statement);
}
sqlite3_close(db);
}
}
So here is the one that messes memory up:
- (IndoorLocation *) getIndoorLocationWithId:(NSNumber *) locId {
if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) {
if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
//if I comment the thing below it works
NSNumber *_id = [[NSNumber alloc] initWithInt:(int) sqlite3_column_int(statement, 5)];
toReturn.outLoc = [self getOutdoorLocationWithId:_id];
[_id release];
}
sqlite3_finalize(statement);
}
sqlite3_close(db);
}
}
So in the one that messes memory up, I use exactly the same function as the first time (getOutdoorLocationwithId), in the same way but it doesn't work, sqlite3 objects don't get released properly.
I hope you understand my problem, this is driving me nuts!
In a single user app like this, there's no need to continually open and close. This is not like a server based app where you have connection pooling with multiple users where holding the connection can defeat the pool and hurt scalability. In a phone app open it and hold it open. close it when you're done. At a minimum, do that within a recursive call.
To make it worse, you're opening and closing within recursive calls - just leave it open.
Also:
You're not checking the return codes of finalize
You're not checking the return codes of close.
You're preparing (compiling) the statments but you're not saving them off - call reset on the saved statement and execute it again.
Consider using FMDB - it's a good wrapper.
BTW, here's a richer and more durable close but don't use it on every call. Close it when you're ending or your app is going into the background ... This is mine which is similar to what fmdb does.
- (void)close
{
if (_sqlite3)
{
ENInfo(#"closing");
[self clearStatementCache];
int rc = sqlite3_close(_sqlite3);
ENDebug(#"close rc=%d", rc);
if (rc == SQLITE_BUSY)
{
ENError(#"SQLITE_BUSY: not all statements cleanly finalized");
sqlite3_stmt *stmt;
while ((stmt = sqlite3_next_stmt(_sqlite3, 0x00)) != 0)
{
ENDebug(#"finalizing stmt");
sqlite3_finalize(stmt);
}
rc = sqlite3_close(_sqlite3);
}
if (rc != SQLITE_OK)
{
ENError(#"close not OK. rc=%d", rc);
}
_sqlite3 = NULL;
}
}
I met the same issue.
When i use and open FMDB, it works OK. but in other callbacks it failed and threw exception of "Closing leaked statement." Finally I found that's because i kept the pointer from [FMDatabase databaseWithPath:dbPath] and the pointer is a autorelease object. I fixed the issue by this:
FMDatabase *db = [[FMDatabase databaseWithPath:dbPath] retain];
and when you want to close the database:
db [close];
[db release];
db = nil;
By this, you don't always open and close database for each operation, the should be a manager object accounting for it. When the manager starts, the database is open always until the manager stops.