blob: c8fda75f8bf65864f2aaa701ffecbf48d18dc96c [file] [view]
# Ref count testing
How to write a reliable ref counting test:
- Rather than trying to read the object ref count, use `ReferenceTracker`.
Alternatively, arc_test and ref_count_test use a `int32_t* counter` pattern,
but the idea is the same. Rather than checking the actual ref counts, these
patterns just watch for when the destructor is called.
- Move creation of any ObjC objects, blocks, or lambda functions into a separate
function, to avoid accidental capturing of variables inside closures, or other
unexpected variable lifetimes.
- Mark such functions with `@pragma('vm:never-inline')`.
- An alternative to serparate functions, to force drop Dart references to an
object, is to make the object variable nullable, and then set it to null.
- Use `expect(object, isNotNull)` to ensure that `object` stays alive until
then.
- Use `doGC()` to force run GC. If you're on a platform that doesn't support
`doGC()` (ie `canDoGC` is false), use `flutterDoGC()` (though it's slower,
not 100% reliable, and breaks autorelease pools, so should be avoided).
- If you're working with blocks, you'll likely need to
`await Future<void>.delayed(Duration.zero)` to allow the disposal message to
be sent back to the owner isolate so that the registered closure is cleaned
up. After that you'll need to call `doGC()` again to make sure the closure is
destroyed.
- You can use `blockHasRegisteredClosure` to test that the closure is destroyed.
- Got nested blocks? Repeat that process until everything is destroyed.
- Objects still living too long, despite all that? ObjC might be doing an
autorelease somewhere. If your test doesn't have an explicit autorelease pool,
it will be added to a pool that outlives the test (likely thread scoped). To
fix this, wrap your test in `objc_autoreleasePoolPush/Pop`. Make sure not to
do any async/await stuff in between the push and pop.