Reputation: 3485
I have obtained and managed to symbolicate a crash report for my iOS app. However, looking at the stack trace I can't seem to make sense of it.
Thread 0 Crashed:
0 libswiftCore.dylib 0x00000001005d330c specialized _assertionFailure(StaticString, String, file : StaticString, line : UInt, flags : UInt32) -> Never (__hidden#17347_:134)
1 libswiftCore.dylib 0x00000001004d7d2c Error<A where ...>._code.getter (__hidden#18979_:221)
2 libswiftCore.dylib 0x00000001004d7bf4 swift_errorInMain + 0
3 MyAppName 0x00000001000c2190 specialized PersonRepository.hydrate(Row) -> Person (PersonRepository.swift:0)
4 MyAppName 0x00000001000fbebc specialized Database.checkSchemaVersion(connection : Connection) -> () (Database.swift:0)
5 MyAppName 0x00000001000fc254 _TTSf4d_g__TZFC6MyAppName8DatabaseP33_909B711B8156620EE1EFE30EC21C4C0C11getInstancefT_S0_ (Database.swift:0)
6 MyAppName 0x00000001000fade8 static Database.getInstance() -> Database (Database.swift:0)
7 MyAppName 0x00000001000a7a6c TaskRepository.init() -> TaskRepository (TaskRepository.swift:38)
(......)
What makes me scratch my head is how on Earth did it get from level 4 up to level 3.
In my undertanding, it implies that Database.checkSchemaVersion(connection : Connection) -> ()
at some point calls PersonRepository.hydrate(Row) -> Person
- which makes no sense whatsoever.
Here's my full Database class (it's not too large)
// Database.swift
import SQLite
import Foundation
class Database
{
private var connection : Connection?
private static var instance : Database?
private static func getInstance() -> Database
{
if (instance == nil) {
instance = Database()
instance!.checkSchemaVersion(connection: instance!.connection!)
}
return instance!
}
private init()
{
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
connection = try! Connection("(path)/myappname.sqlite3");
}
deinit {
connection = nil
}
static func getConnection() -> Connection
{
return getInstance().connection!
}
func checkSchemaVersion(connection : Connection)
{
guard let buildString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
Application.debug("Database: could not convert Build to String")
return
}
guard let build = Int64(buildString) else {
Application.debug("Database: could not convert Build from String to Int64")
return
}
let schemaVersion = try! connection.scalar("PRAGMA user_version") as? Int64 ?? 0
Application.debug("Database: checking schema version - DB=(schemaVersion) vs app=(build)")
if schemaVersion < build {
Application.debug("Database: old schema version detected - updating now")
PersonRepository.getInstance().updateSchema(from: schemaVersion, to: build)
PictureRepository.getInstance().updateSchema(from: schemaVersion, to: build)
TaskRepository.getInstance().updateSchema(from: schemaVersion, to: build)
TaskCommentRepository.getInstance().updateSchema(from: schemaVersion, to: build)
VenueRepository.getInstance().updateSchema(from: schemaVersion, to: build)
try! connection.run("PRAGMA user_version = (build)")
}
}
}
Any ideas what that stack trace (isn't very stacky, huh?) supposed to mean, or how does it arrive at this situation? No wonder it crashes if things are actually going in such a way.
UPDATE
Although I believe that stack trace says that somewhere in Database.checkSchemaVersion(connection : Connection) -> ()
there is a direct call to PersonRepository.hydrate(Row) -> Person
and this addition is extraneous, here's the relevant section of PersonRepository
private init()
{
db = Database.getConnection()
table = Table("persons")
pictureRepository = PictureRepository.getInstance()
try! db.run(table.create(ifNotExists: true) {
t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email)
t.column(phone)
t.column(company)
t.column(pictureId)
})
}
public func updateSchema(from: Int64, to: Int64)
{
if from < 2016121201 {
try! db.run(table.addColumn(active, defaultValue: 1))
}
}
static func getInstance() -> PersonRepository
{
if (instance == nil) {
instance = PersonRepository()
}
return instance!
}
Upvotes: 2
Views: 3720
Reputation: 1180
One tip - avoid !
as much as possible. Especially for exception handling with try
. There should be a good reason why things can throw and I believe this is the reason of your crash.
The stack trace might not be completely obvious because some of the method calls can be inlined/optimised by the compiler for release builds. In your case it's probably happens this way:
checkSchemaVersion(connection : Connection)
PersonRepository.getInstance()
PersonRepository.init()
try! db.run()
Database
If you cannot reproduce the crash my advice would be to rewrite this code without force-unwrap and thinking what to do in case something goes wrong. One solution could be to use failable initialisers and handle possible nil
values upstream, i.e.
private init?()
{
db = Database.getConnection()
table = Table("persons")
pictureRepository = PictureRepository.getInstance()
do {
try db.run(table.create(ifNotExists: true) {
t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email)
t.column(phone)
t.column(company)
t.column(pictureId)
})
} catch {
return nil
}
}
Upvotes: 1