Apple’s CoreData has been a robust datastore solution since iOS3, as we all know. However, I really don’t like it for the reasons I will try to explain here.  The original solution was the SQLite, which is a fantastic datastore for mobile phones, much simpler than the big SQL servers, like Oracle or mySQL, but still a very useful tool.

SQLite vs Core Data

SQLite is a monolit database file and a dynamic library that helps our app to capture the data from the file and additionally to save it. It’s a native thing, and in my programming philosophy, the native solutions are the most important. When you use additional layers, such as CoreData, it can cost a great deal and sometimes it can obstruct the app running and cause your app to lag. But our modern gadgets have good hardware, strong multicore processors, lots of memory, and so on. If the next layer is really useful it is worth using, but, in my opinion, CoreData is not such a layer.

If you know the SQL language well, you know that you can do everything you could possibly want with it. You can join tables in the queries in a simple way and SQLite knows almost every opportunity of SQL except for stored procedures. CoreData, on the other hand, gives you a barely learnable command set and an over-complicated AppDelegate for the datastore, but based on the same SQLite database. Unfortunately, you don’t have an auto incremented index field (oops, there is, only you can’t see or use it) so you have to solve with a new Integer field which you also have to increment programmatically. Oh my God, what if you use parallel ManagedObjectContext? You can’t make a custom index field because it could be the same.

CoreData gives you a class for every Table (no, it is actually Entity, only you also have to learn a huge amount of new terminus), which is a good news. Or not. Because you can’t create an instance of that class, it would be too simple. In Objective C, to make an SQLite query was a bit complicated (no, it isn’t true if you use function with variable arguments like [NSString stringWithFormat:]) but in Swift it’s a really easy role. So far, SQLite is much easier.

The only instance, in which I prefer CoreData, is the iCloud database sync between device, which would be a super opportunity if it actually worked. Sometimes it does work well. But sometimes it doesn’t. And testing that sync is a nightmare for developers, you have to test it on Sandbox mode until you have published the app and it doesn’t work in most cases. If you try it and compare it to another solution (for example Dropbox), you’ll agree with me. Additionally, if you use iCloud for sync, you can change data only between your iDevices, not other devices with different platforms. Subscribe to our blog for more information about Dropbox datastore sync, which will feature in one of my next posts.

One more thing, if you write your database in CoreData, you can’t port to other platforms easily because there is no CoreData on other operation systems. But if you use the native SQLite, you can use the databases without changing it everywhere, and this means, not only the database,but all queries, as well.

These are the reasons why I prefer SQLite in iOS development. I know I should move with the times, but when I think about a new solution it is a dead end (as was the XML against JSON, in my opinion) - I don’t want to follow it. For SQLite, I’d like to give you a piece of advice: please use enough indices for the tables. Indices can fast up the queries very well. And if you don’t know how to use SQLite on a native mode, I’d like to show you an SQLiteHelper class, which I hope will be of help.

class SQLiteHelper: NSObject {

    private var dbPath = ""
    private var database: COpaquePointer = nil
    private var opened = false

    class var instance: SQLiteHelper {
        struct Static {
            static let instance: SQLiteHelper = SQLiteHelper()
        }
        return Static.instance
    }

    func open(databaseName: String, inDocuments: Bool) {
        /*
        inDocuments says if you want to copy the database into the
        Documents folder (read/write) or leave it in resources (read only)
        */

        var resPath = ""
        if (inDocuments) {
            dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
            resPath = NSBundle.mainBundle().resourcePath! +  "/" + databaseName
        } else {
            dbPath = NSBundle.mainBundle().resourcePath!
        }
        dbPath += "/" + databaseName
        println(dbPath)

        var error: NSError
        if (!NSFileManager.defaultManager().fileExistsAtPath(dbPath)) {
            NSFileManager.defaultManager().copyItemAtPath(resPath, toPath: dbPath, error: nil)
        }
        opened = (sqlite3_open(dbPath.cStringUsingEncoding(NSUTF8StringEncoding)!, &database) == SQLITE_OK)
        if (!opened) {
            println("Cannot open database!")
        }

    }

    func query(sqlStr: String) -> Array> {
        var result: Array> = []
        if (opened) {
            var stmt: COpaquePointer = nil
            var sql = sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)
            if (sqlite3_prepare_v2(database, sql!, -1, &stmt, nil) == SQLITE_OK) {
                while(sqlite3_step(stmt) == SQLITE_ROW) {
                    var line = Dictionary()
                    for var i: Int32 = 0; i < sqlite3_column_count(stmt); i++ {
                        let buffer = UnsafePointer(sqlite3_column_name(stmt, i))
                        let field = String.fromCString(buffer) as String!
                        let buffer2 = UnsafePointer(sqlite3_column_text(stmt, i))
                        let value = String.fromCString(buffer2)
                        line[field] = value
                    }
                    result.append(line)
                }
            } else {
                println("SQL error: (sqlite3_errmsg(database).debugDescription) (query)!")
            }
            sqlite3_finalize(stmt)
        } else {
            println("Cannot open database!")
        }
        return result
    }

    func execute(sqlStr: String) -> Int {
        var result = 0
        if (opened) {
            var stmt: COpaquePointer = nil
            var sql = sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)
            sqlite3_exec(database, "BEGIN EXCLUSIVE TRANSACTION", nil, nil, nil);
            if (sqlite3_prepare_v2(database, sql!, -1, &stmt, nil) == SQLITE_OK) {
                var status = sqlite3_step(stmt)
                if (status == SQLITE_DONE) {
                    result = 1;
                    if (sqlite3_last_insert_rowid(database) > 0) {
                        result = Int(sqlite3_last_insert_rowid(database))
                    }
                }
            } else {
                println("SQL error: (sqlite3_errmsg(database).debugDescription) (query)!")
            }
            if (sqlite3_exec(database, "COMMIT TRANSACTION", nil, nil, nil) != SQLITE_OK) {
                println("SQL Error: (sqlite3_errmsg(database))");
            }
            sqlite3_finalize(stmt)
        } else {
            println("Cannot open database!")
            return result
        }
        return result
    }
}