Tuesday, December 15, 2015

How To Use SQLite to Manage Data in iOS Apps

iOS SQLite Database


                                                               Sqlite.h


//
//  Sqlite.h
//

#import <Foundation/Foundation.h>
#import <sqlite3.h>

@interface Sqlite : NSObject {
NSInteger busyRetryTimeout;
NSString *filePath;
sqlite3 *_db;
}

@property (readwriteNSInteger busyRetryTimeout;
@property (readonlyNSString *filePath;

+ (NSString *)createUuid;
+ (NSString *)version;

- (id)initWithFile:(NSString *)filePath;

- (BOOL)open:(NSString *)filePath;
- (void)close;

- (NSInteger)errorCode;
- (NSString *)errorMessage;

- (NSArray *)executeQuery:(NSString *)sql, ...;
- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args;

- (BOOL)executeNonQuery:(NSString *)sql, ...;
- (BOOL)executeNonQuery:(NSString *)sql arguments:(NSArray *)args;

- (BOOL)commit;
- (BOOL)rollback;
- (BOOL)beginTransaction;
- (BOOL)beginDeferredTransaction;

@end


----------------------------------------------------------------------------------------------------------------------

                                                                Sqlite.m

//
//  Sqlite.m
//  ScireNotes
//
//  Created by Alpesh Kothari on 11/9/11.
//  Copyright (c) 2012 ScireInc. All rights reserved.
//

#import "Sqlite.h"

/* ============================================================================
 */
@interface Sqlite (PRIVATE)
- (BOOL)executeStatament:(sqlite3_stmt *)stmt;
- (BOOL)prepareSql:(NSString *)sql inStatament:(sqlite3_stmt **)stmt;
- (void) bindObject:(id)obj toColumn:(int)idx inStatament:(sqlite3_stmt *)stmt;

- (BOOL)hasData:(sqlite3_stmt *)stmt;
- (id)columnData:(sqlite3_stmt *)stmt columnIndex:(NSInteger)index;
- (NSString *)columnName:(sqlite3_stmt *)stmt columnIndex:(NSInteger)index;
@end

@implementation Sqlite

@synthesize busyRetryTimeout;
@synthesize filePath;

+ (NSString *)createUuid {
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
return([(NSString *) uuidStringRef autorelease]);
}

+ (NSString *)version {
return [NSString stringWithFormat:@"%s"sqlite3_libversion()];
}

- (id)init {
if ((self = [super init])) {
busyRetryTimeout = 1;
filePath = nil;
_db = nil;
}
    
return self;
}

- (id)initWithFile:(NSString *)dbFilePath {
if (self = [super init]) {
[self open:dbFilePath];
}
return self;
}

- (void)dealloc {
[self close];
    
[super dealloc];
}

- (BOOL)open:(NSString *)path {
[self close];
if (sqlite3_open([path fileSystemRepresentation], &_db) != SQLITE_OK) {
NSLog(@"SQLite Opening Error: %s"sqlite3_errmsg(_db));
return NO;
}
    
filePath = [path retain];
return YES;
}

- (void)close {
if (_db == nilreturn;
    
int numOfRetries = 0;
int rc;
do {
rc = sqlite3_close(_db);
if (rc == SQLITE_OK)
break;
        
if (rc == SQLITE_BUSY) {
usleep(20);
            
if (numOfRetries == busyRetryTimeout) {
NSLog(@"SQLite Busy, unable to close: %@"filePath);
break;
}
else {
NSLog(@"SQLite %@ Closing Error: %s"filePathsqlite3_errmsg(_db));
break;
}
while (numOfRetries++ > busyRetryTimeout);
    
[filePath release];
filePath = nil;
_db = nil;
}

- (NSInteger)errorCode {
return sqlite3_errcode(_db);
}

- (NSString *)errorMessage {
return [NSString stringWithFormat:@"%s"sqlite3_errmsg(_db)];
}

- (NSArray *)executeQuery:(NSString *)sql, ... {
va_list args;
va_start(args, sql);
    
NSMutableArray *argsArray = [[NSMutableArray allocinit];
NSUInteger i;
for (i = 0; i < [sql length]; ++i) {
if ([sql characterAtIndex:i] == '?')
[argsArray addObject:va_arg(args, id)];
}
va_end(args);
NSArray *result = [self executeQuery:sql arguments:argsArray];
    
[argsArray release];
return result;
}

- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
    
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
    
NSMutableArray *arrayList = [[NSMutableArray allocinit];
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary allocinit];
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
            // id columnData = [self columnData:sqlStmt columnIndex:i];
            NSString *columnData= [NSString stringWithUTF8String:(constchar*)sqlite3_column_text(sqlStmt, i)];
            //NSLog(@"%@",columnData);
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:[dictionary autorelease]];
}
    
sqlite3_finalize(sqlStmt);
    
return arrayList;
}

- (BOOL)executeNonQuery:(NSString *)sql, ... {
va_list args;
va_start(args, sql);
    
NSMutableArray *argsArray = [[NSMutableArray allocinit];
    // KP we currently have issue with having "?" in any text we want to insert.
    // To remedy this as a temporary hack I am removing the follwing check
    // This means that we will not be ablt to use "?" and associated variable
    // in a query. Currently we are not using any such query so we are safe
    // and we will not be breaking any functionality.
    
    //    NSUInteger i;
    // for (i = 0; i < [sql length]; ++i) {
    // if ([sql characterAtIndex:i] == '?')
    // [argsArray addObject:va_arg(args, id)];
    // }
va_end(args);
BOOL success = [self executeNonQuery:sql arguments:argsArray];
    
[argsArray release];
return success;
}

- (BOOL)executeNonQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
    
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return NO;
    
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
    
BOOL success = [self executeStatament:sqlStmt];
    
sqlite3_finalize(sqlStmt);
return success;
}

- (BOOL)commit {
return [self executeNonQuery:@"COMMIT TRANSACTION;"];
}

- (BOOL)rollback {
return [self executeNonQuery:@"ROLLBACK TRANSACTION;"];
}

- (BOOL)beginTransaction {
return [self executeNonQuery:@"BEGIN EXCLUSIVE TRANSACTION;"];
}

- (BOOL)beginDeferredTransaction {
return [self executeNonQuery:@"BEGIN DEFERRED TRANSACTION;"];
}

/* ============================================================================
 *  PRIVATE Methods
 */

- (BOOL)prepareSql:(NSString *)sql inStatament:(sqlite3_stmt **)stmt {
int numOfRetries = 0;
int rc;
    
do {
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, stmt, NULL);
if (rc == SQLITE_OK)
return YES;
        
if (rc == SQLITE_BUSY) {
usleep(20);
            
if (numOfRetries == busyRetryTimeout) {
NSLog(@"SQLite Busy: %@"filePath);
break;
}
else {
NSLog(@"SQLite Prepare Failed: %s"sqlite3_errmsg(_db));
NSLog(@" - Query: %@", sql);
break;
}
while (numOfRetries++ > busyRetryTimeout);
    
return NO;
}

- (BOOL)executeStatament:(sqlite3_stmt *)stmt {
int numOfRetries = 0;
int rc;
    
do {
rc = sqlite3_step(stmt);
if (rc == SQLITE_OK || rc == SQLITE_DONE)
return YES;
        
if (rc == SQLITE_BUSY) {
usleep(20);
            
if (numOfRetries == busyRetryTimeout) {
NSLog(@"SQLite Busy: %@"filePath);
break;
}
else {
NSLog(@"SQLite Step Failed: %s"sqlite3_errmsg(_db));
break;
}
while (numOfRetries++ > busyRetryTimeout);
    
return NO;
}

- (void)bindObject:(id)obj toColumn:(int)idx inStatament:(sqlite3_stmt *)stmt {
if (obj == nil || obj == [NSNull null]) {
sqlite3_bind_null(stmt, idx);
else if ([obj isKindOfClass:[NSData class]]) {
sqlite3_bind_blob(stmt, idx, [obj bytes], [obj length], SQLITE_STATIC);
else if ([obj isKindOfClass:[NSDate class]]) {
sqlite3_bind_double(stmt, idx, [obj timeIntervalSince1970]);
else if ([obj isKindOfClass:[NSNumber class]]) {
if (!strcmp([obj objCType], @encode(BOOL))) {
sqlite3_bind_int(stmt, idx, [obj boolValue] ? 1 : 0);
else if (!strcmp([obj objCType], @encode(int))) {
sqlite3_bind_int64(stmt, idx, [obj longValue]);
else if (!strcmp([obj objCType], @encode(long))) {
sqlite3_bind_int64(stmt, idx, [obj longValue]);
else if (!strcmp([obj objCType], @encode(float))) {
sqlite3_bind_double(stmt, idx, [obj floatValue]);
else if (!strcmp([obj objCType], @encode(double))) {
sqlite3_bind_double(stmt, idx, [obj doubleValue]);
else {
sqlite3_bind_text(stmt, idx, [[obj descriptionUTF8String], -1SQLITE_STATIC);
}
else {
sqlite3_bind_text(stmt, idx, [[obj descriptionUTF8String], -1SQLITE_STATIC);
}
}

- (BOOL)hasData:(sqlite3_stmt *)stmt {
int numOfRetries = 0;
int rc;
    
do {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
return YES;
        
if (rc == SQLITE_DONE)
break;
        
if (rc == SQLITE_BUSY) {
usleep(20);
            
if (numOfRetries == busyRetryTimeout) {
NSLog(@"SQLite Busy: %@"filePath);
break;
}
else {
NSLog(@"SQLite Prepare Failed: %s"sqlite3_errmsg(_db));
break;
}
while (numOfRetries++ > busyRetryTimeout);
    
return NO;
}

- (id)columnData:(sqlite3_stmt *)stmt columnIndex:(NSInteger)index {
int columnType = sqlite3_column_type(stmt, index);
    
if (columnType == SQLITE_NULL)
return([NSNull null]);
    
if (columnType == SQLITE_INTEGER)
return [NSNumber numberWithInt:sqlite3_column_int(stmt, index)];
    
if (columnType == SQLITE_FLOAT)
return [NSNumber numberWithDouble:sqlite3_column_double(stmt, index)];
    
if (columnType == SQLITE_TEXT) {
const unsigned char *text = sqlite3_column_text(stmt, index);
return [NSString stringWithFormat:@"%s", text];
}
    
if (columnType == SQLITE_BLOB) {
int nbytes = sqlite3_column_bytes(stmt, index);
const char *bytes = sqlite3_column_blob(stmt, index);
return [NSData dataWithBytes:bytes length:nbytes];
}
    
return nil;
}

- (NSString *)columnName:(sqlite3_stmt *)stmt columnIndex:(NSInteger)index {
    return [NSString stringWithUTF8String:sqlite3_column_name(stmt, index)];

return [NSString stringWithUTF8String:sqlite3_column_name(stmt, index)];
}

@end


----------------------------------------------------------------------------------------------------------------------

                                                                Appdelegate.m



@synthesize dbObject;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    
    self.window = [[UIWindow allocinitWithFrame:[[UIScreen mainScreenbounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor clearColor];
    
    //initialize and mount SQLite========================//
    self.dbObject = [[Sqlite allocinit];
    NSLog(@"get DB : %d",[self.dbObject open:[self getDBPath]]);
    //=====================================//

    [[UIApplication sharedApplicationsetStatusBarStyle:UIStatusBarStyleLightContent];
    
    //[self.window setBackgroundColor:[UIColor colorWithRed:(41.0/255.0green:(47.0/255.0blue:(52.0/255.0alpha:0.5]];
    
    // define navigation controller and main view in window
    mainView = [[MainViewController alloc]init];
    navController = [[UINavigationController allocinitWithRootViewController:mainView];
    navController.navigationBarHidden = YES;
    [self.window setRootViewController:navController];
    
    [self.window makeKeyAndVisible];
    return YES;
}


// returns database path
//-------------------------------------------------------------------------------------------------------------------------------------------------
- (NSString *)getDBPath{
    //-------------------------------------------------------------------------------------------------------------------------------------------------
    BOOL success = FALSE;
    NSString *databaseName = @"YourFileName.sqlite";
    
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMaskYES);
    NSString *documentsDir = [documentPaths objectAtIndex:0];
    NSString *databasePath = documentsDir;
    databasePath = [databasePath stringByAppendingString:[NSStringstringWithFormat:@"/%@",databaseName]];
    // Create a FileManager object, we will use this to check the status
    // of the database and to copy it over if required
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    // Check if the database has already been created in the users filesystem
    success = [fileManager fileExistsAtPath:databasePath];
    
    // If the database already exists then return without doing anything
    if(success) {
        
        NSLog(@"SQLLite file exists ! no need to copy");
    }
    else
    {
        // Get the path to the database in the application package
        NSString *databasePathFromApp = [[[NSBundle mainBundleresourcePath]stringByAppendingPathComponent:databaseName];
        NSError *error11;
        // Copy the database from the package to the users filesystem
        [fileManager copyItemAtPath:databasePathFromApp toPath:databasePatherror:&error11];
    }
    return databasePath;
}

----------------------------------------------------------------------------------------------------------------------

                                                                Appdelegate.h

@property (strongnonatomicSqlite *dbObject;

No comments:

Post a Comment