Transactions + Protocols Best Practices #1170
-
I've been playing with GRDB for my latest iOS project's storage needs and love all that it provides. Looking for some guidance on the best practices for handling a certain scenario of abstracting GRDB and transactions behind a protocol. Given this example of a counter object and it's storage protocol:
When bulk processing counters from a server response we want to group all updates into a single transaction to keep the UI from updating until all updates are processed:
How can we abstract this away using GRDB? A basic, standard implementation like:
Throws fatal errors because Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hello @matt-dewitt, It looks like you could write working code with struct CounterGRDBInMemoryStorage: CounterStorageProtocol {
func save(counter: Counter) throws {
try dbQueue.unsafeReentrantWrite { db in
try counter.insert(db)
}
}
func doTransaction(_ transactionBlock: () throws -> Void) throws {
try dbQueue.unsafeReentrantWrite { db in
try db.inTransaction {
try transactionBlock()
return .commit
}
}
}
} You must use "unsafe" methods, because // Sure, why not?
for counter in counters {
counterStorage.save(counter)
}
// Why bother?
counterStorage.doTransaction {
for counter in counters {
counterStorage.save(counter)
}
} The first version is easier to write than the second, more correct, version. Not only is the protocol easy to misuse, it actually fosters misuse. There lies the reason why you need to use "unsafe" methods. Of course, in your specific application, the actual possibility for misuse can be very low. So you do not have to worry about the use of "unsafe" methods. Maybe write a documentation comment that explains when one should remember to use Beware how you handle your transactions. The code below is not correct: // DON'T DO THAT
try dbQueue.inDatabase { db in
try db.beginTransaction()
try transactionBlock()
try db.commit()
} It is not correct, because errors thrown by Instead, use // CORRECT
try dbQueue.inDatabase { db in
try db.inTransaction {
try transactionBlock()
return .commit
}
} Maybe a future GRDB version will rename |
Beta Was this translation helpful? Give feedback.
Hello @matt-dewitt,
It looks like you could write working code with
unsafeReentrant
methods, and this looks fine to me. I expect something like:You must use "unsafe" methods, because
CounterStorageProtocol
does not force the user to thi…