I am experiencing a memory leak issue when running the app on an arm64 device (tested on iPhone 6 and iPad mini 2) when running from Xcode in debug mode. I am using the latest Xcode 7.0.1 and both devices have latest iOS 9.0.2.
It is worth mentioning that :
- this memory leak only happens when running the app from Xcode in debug mode. When running the app directly on the device, there is no memory leak.
- the memory leak only appears on arm64 devices (I have tested on iPad 2 and iPad 3 and there was no memory leak)
- the memory leak does not happen when running SqlCipher without encryption (when not setting the encryption key)
- the memory leak does not happen when using a previously compiled version of the SQLCipher library (which I had compiled from the open source repo about 2 years ago)
- I have double checked and zombie pointers are not enabled
Here are the relevant bits of code from my very simple test app, which just repeatedly inserts a thousand records before waiting one second (BTW, I know that it would be much more efficient to include this inserts inside a transaction, but this is not the point)
AppDelegate.m
- (NSURL *)databaseURL {
NSURL *directoryURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
return [directoryURL URLByAppendingPathComponent:@"secure.db"];
}
//Repeatedly inserts 1000 rows in the table and waits for 1 second
- (void) run {
dispatch_async(dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL), ^{
sqlite3 *db;
if (sqlite3_open([[self.databaseURL path] UTF8String], &db) != SQLITE_OK) {
return NSLog(@"Could not open database");
}
const char* key = [@"StrongPassword" UTF8String];
sqlite3_key(db, key, (int)strlen(key));
if (sqlite3_exec(db, (const char*) "SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL) != SQLITE_OK) {
return NSLog(@"Incorrect password!");
}
if (sqlite3_exec(db, (const char*) "CREATE TABLE IF NOT EXISTS cards (cardID INTEGER PRIMARY KEY NOT NULL);", NULL, NULL, NULL) != SQLITE_OK) {
return NSLog(@"Could not create table");
}
for (long i = 0; i < 1000; i++) {
NSLog(@"%ld", i);
NSString* sql = [NSString stringWithFormat:@"INSERT OR REPLACE INTO cards (cardID) VALUES (%ld);", i];
if (sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL) != SQLITE_OK) {
return NSLog(@"Could not insert card");
}
}
sqlite3_close(db);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self run];
});
});
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self run];
return YES;
}
ViewController.m
- (double) memoryUsage {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if( kerr == KERN_SUCCESS ) {
return (double)info.resident_size/1024/1024;
} else {
return 0;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
label = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, self.view.bounds.size.width - 40, 40)];
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
label.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:label];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerDidFire) userInfo:nil repeats:YES];
}
- (void) timerDidFire {
label.text = [NSString stringWithFormat:@"%.1f MB", [self memoryUsage]];
}