Objective-C Threading topic
Objective-C threading considerations
Multithreading is one of the trickiest parts of interop between Objective-C and Dart. This is due to the relationship between Dart isolates and OS threads, and the way Apple's APIs handle multithreading:
- Dart isolates are not the same thing as threads. Isolates run on threads, but aren't guaranteed to run on any particular thread, and the VM might change which thread an isolate is running on without warning. There is an open feature request to enable isolates to be pinned to specific threads.
- While FFIgen supports converting Dart functions to Objective-C blocks, most Apple APIs don't make any guarantees about which thread a callback will run on.
- Most APIs that involve UI interaction can only be called on the main thread, also called the platform thread in Flutter.
- Many Apple APIs are not thread safe.
The first two points mean that a block created in one isolate might be invoked on a thread running a different isolate, or no isolate at all. When a block is created in Dart, the isolate it was created in is its owner. The block is permanently tied to that isolate, and the Dart function it wraps will always be executed on the owner isolate.
Important
Depending on the type of block you are using, invoking it on the wrong thread
could cause your app to crash. If possible, you should use a .listener
or .blocking block to avoid this issue.
- Blocks created using
FooBlock.fromFunctionmust be invoked on the owner isolate's thread, otherwise they will crash. If the block is invoked after the owner isolate has shut down, it will also crash. - Blocks created using
FooBlock.listenercan safely be invoked from any thread. These blocks are asynchronous, and the Dart function will be invoked on the owner isolate at a later time. Return values are not supported, so this constructor is only code generated for blocks that returnvoid. If the owner isolate shuts down before the block is invoked, the invocation will be silently ignored. - Blocks created using
FooBlock.blockingcan be safely invoked from any thread, and the Dart function will be invoked on the owner isolate. The caller will be blocked until the Dart function has completed. If the owner isolate shuts down before the block is invoked, the invocation will be silently ignored. Return values are not currently supported, but it would be possible to add support for non-voidblocking blocks, if there is user demand for it. See #3697.
The third point means that directly calling some Apple APIs using the
generated Dart bindings might be thread unsafe. This could crash your app, or
cause other unpredictable behavior. In recent versions of Flutter, the main
isolate runs on the platform thread, so this isn't an issue when invoking
these thread-locked APIs from the main isolate. If you need to invoke these
APIs from other isolates, or you need to support older versions of flutter,
you can use the runOnPlatformThread function.
Regarding the fourth point, although Dart isolates can switch threads, they only ever run on one thread at a time. So, the API you are interacting with doesn't necessarily have to be thread safe, as long as it is not thread hostile, and doesn't have constraints about which thread it's called from.
You can safely interact with Objective-C code as long as you keep these limitations in mind.
Classes
- FfiGenerator Apple APIs Objective-C Memory Management Objective-C Method Filtering Dealing with OS Differences Objective-C Runtime Types Objective-C Threading Errors FAQ
-
The generator that generates bindings for
dart:ffifrom C and Objective-C headers.