FMDB
SQLite
Crash
iOS Development
Debugging

FMDBBlockSQLiteCallBackFunction Crash in app that's not using makeFunctionNamed

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A crash in FMDBBlockSQLiteCallBackFunction can look confusing when your code never calls makeFunctionNamed directly. In practice, the callback can still be involved through internal SQL execution paths, extension points, or stale function registration state. This guide shows how to isolate the root cause and harden your FMDB usage.

What This Crash Usually Means

FMDBBlockSQLiteCallBackFunction is tied to block-based SQLite function registration in FMDB. Even when your app does not explicitly register custom SQL functions, the crash can surface due to:

  • Third-party code paths that register functions on the same database handle.
  • Deallocated objects captured by callback blocks.
  • Using one database connection across multiple threads unsafely.
  • Memory corruption around SQLite statement lifecycle.

The stack frame is often a symptom, not the original bug.

Reproduce with a Controlled Setup

Start by reducing your database layer to a minimal test case.

objc
1FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
2
3[queue inDatabase:^(FMDatabase *db) {
4    [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY, name TEXT)"];
5    [db executeUpdate:@"INSERT INTO t (name) VALUES (?)", @"sample"];
6
7    FMResultSet *rs = [db executeQuery:@"SELECT id, name FROM t"];
8    while ([rs next]) {
9        NSLog(@"%@", [rs stringForColumn:@"name"]);
10    }
11    [rs close];
12}];

If the minimal case is stable, re-enable features gradually to find the trigger.

Verify Thread Safety First

FMDB objects are not universally thread-safe. Prefer FMDatabaseQueue and avoid sharing one FMDatabase instance across concurrent threads.

swift
1let queue = FMDatabaseQueue(path: dbPath)
2
3queue.inDatabase { db in
4    _ = db.executeUpdate("INSERT INTO logs(message) VALUES(?)", withArgumentsIn: ["ok"])
5}

If multiple modules use the database, enforce one queue per database file and prohibit direct FMDatabase usage outside queue blocks.

Watch Object Lifetimes in Blocks

If callbacks capture temporary objects, they may be released before SQLite executes related work. Keep dependencies strongly referenced for the full query lifecycle.

objc
1__block NSString *prefix = [@"event" copy];
2[queue inDatabase:^(FMDatabase *db) {
3    NSString *value = [prefix stringByAppendingString:@"_1"];
4    [db executeUpdate:@"INSERT INTO events(name) VALUES (?)", value];
5}];

Avoid storing unsafe pointers or mutable shared state inside callback-based code paths.

Inspect SQL and Result Handling

Invalid SQL, unclosed result sets, or schema mismatch can destabilize long-lived sessions.

objc
1[db setTraceExecution:YES];
2[db setLogsErrors:YES];
3
4FMResultSet *rs = [db executeQuery:@"SELECT * FROM users WHERE id = ?", @(userId)];
5while ([rs next]) {
6    // consume values
7}
8[rs close];

Enable tracing in debug builds and verify every query path closes FMResultSet.

Use Symbolic Breakpoints and Sanitizers

Add a symbolic breakpoint on FMDBBlockSQLiteCallBackFunction, then inspect thread, SQL statement, and captured objects. Combine with Xcode Address Sanitizer and Thread Sanitizer for memory and race detection.

Practical workflow:

  • Reproduce on debug build with sanitizers.
  • Capture exact SQL before crash.
  • Check whether callback block references stale memory.
  • Confirm queue discipline and connection ownership.

Defensive Hardening Checklist

  • Pin FMDB and SQLite versions across environments.
  • Avoid mixing raw SQLite C API calls with FMDB on the same handle.
  • Keep schema migrations serialized and atomic.
  • Reopen database cleanly after migration failures.
  • Add integration tests that exercise high-concurrency query paths.

These controls reduce rare callback crashes that only appear under production load.

Common Pitfalls

  • Assuming no custom function path exists just because app code does not call makeFunctionNamed directly.
  • Sharing one FMDatabase instance across concurrent threads.
  • Forgetting to close result sets, causing long-lived handle pressure.
  • Capturing short-lived objects in blocks that execute later.
  • Debugging only the crash frame instead of tracing the SQL path that triggered it.

Summary

  • FMDBBlockSQLiteCallBackFunction crashes are often secondary symptoms.
  • Start from a minimal reproducible case and reintroduce complexity incrementally.
  • Enforce queue-based access and strict object lifetime rules.
  • Trace SQL and close result sets on every path.
  • Use sanitizers and symbolic breakpoints to find memory or concurrency root causes.

Course illustration
Course illustration

All Rights Reserved.