Moon Cheesez Moon Cheesez - 3 months ago 17
Swift Question

Execute code before leaving function

I am currently working on some code for an

sqlite
database. I noticed that after preparing a query, I would always need to finalize the query (
sqlite3_finalize(statementPointer)
) before I exit the function. Is there a way to do this other than fill in all the possibilities?

For example:

func updateColumn(db: COpaquePointer, name: String, x: sqlite3_int64, y: String!=nil) -> Bool {
var statement = "UPDATE MY_TABLE SET X=?"
var statementPointer: COpaquePointer = nil
if y != nil {
statement += ", Y=?"
}
statement += " WHERE NAME=?"
if sqlite3_prepare_v2(db, statement, -1, &statementPointer, nil) != SQLITE_OK {
return false
} else if sqlite3_bind_int64(statementPointer, 1, x) != SQLITE_OK {
// Note this code here
sqlite3_finalize(statementPointer)
return false
}

if y != nil {
if sqlite3_bind_text(statementPointer, 2, y, -1, nil) != SQLITE_OK {
// Note this repetition
sqlite3_finalize(statementPointer)
return false
}
}

if sqlite3_step(statementPointer) != SQLITE_DONE {
// Note this repetition
sqlite3_finalize(statementPointer)
return false
}
// Note this repetition
sqlite3_finalize(statementPointer)
return true
}


Of course, this is just something I came up to illustrate this. In the real code, there are many other
if
clauses which I need to finalize their statements.

I understand that this is something like
deinit
for classes in swift but are there
deinit
s for functions as well?

For example (code that I would like it to be like but doesn't work):

func updateColumn(params...) -> Bool {
// code...
deinit {
sqlite3_finalize(statementPointer)
}
}

Answer

Yes, there is a "deinit for functions" — it's called defer:

A defer statement is used for executing code just before transferring program control outside of the scope that the defer statement appears in.

Note that unlike your hypothetical example, the defer statement must appear before whatever might happen to cause the cleanup it performs, not at the end of the enclosing scope. In general, it works like this:

func doStuff() {
    let resource = acquireResource()
    defer {
        cleanup(resource)
    }
    if something { return }
    doOtherStuff()
}

Here, cleanup(resource) gets called regardless of whether the function exits because of if something or because it reaches the end of its scope (after doOtherStuff()).

You can't put a defer inside an if like you're asking -- it only defers to the exit of the scope it's in, so it'd execute at the end of the if body. But defer does compose well with guard... In your case, you probably want something like this:

func updateColumn(db: COpaquePointer, name: String, x: sqlite3_int64, y: String!=nil) -> Bool {
    var statementPointer: COpaquePointer = nil
    //... Other stuff...

    guard sqlite3_prepare_v2(db, statement, -1, &statementPointer, nil) == SQLITE_OK
        else { return false }
    // after this you want any possible exit to do finalize, so put the defer here
    defer { sqlite3_finalize(statementPointer) }

    // every `return` after here, true or false, will execute the `defer` clause
    guard sqlite3_bind_int64(statementPointer, 1, x) == SQLITE_OK 
        else { return false }

    guard y != nil && sqlite3_bind_text(statementPointer, 2, y, -1, nil) == SQLITE_OK 
        else { return false }

    guard sqlite3_step(statementPointer) == SQLITE_DONE 
        else { return false }

    return true
}
Comments