blob: a800d37228957cca80d26f2e405d7a30e051f21b [file] [view] [edit]
# 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:
1. 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](https://github.com/dart-lang/sdk/issues/46943)
to enable isolates to be pinned to specific threads.
2. 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.
3. Most APIs that involve UI interaction can only be called on the main
thread, also called the platform thread in Flutter.
4. Many Apple APIs are [not thread safe](
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html).
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.fromFunction` must 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.listener` can 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 return `void`. If the
owner isolate shuts down before the block is invoked, the invocation will be
silently ignored.
- Blocks created using `FooBlock.blocking` can 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-`void` blocking blocks, if there is
user demand for it. See [#3697](https://github.com/dart-lang/native/issues/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`](
https://api.flutter.dev/flutter/dart-ui/runOnPlatformThread.html) 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.