Roughly speaking, both of these quotes are abstract arguments for asynchronous programming patterns, but neither was intended as a categorical prohibition against ever blocking any thread.
Perhaps it goes without saying, but on both platforms, we never block the main thread. It leads to an unresponsive or stuttering UI. (And if it happens at the wrong time on iOS, at least, might result in your app being summarily terminated for non-responsiveness.) On both platforms, the relevant asynchronous patterns are designed largely to avoid blocking the main thread.
But, regarding background threads, we would still favor asynchronous API over synchronous ones:
Asynchronous API frequently offer greater flexibility. For example, asynchronous API often offer the capability to cancel operations that are underway, something that synchronous API rarely support.
In iOS, the number of available background threads is surprisingly limited. If you block too many threads in “thread explosion” scenarios (e.g., more than 64 threads), you can exhaust the GCD worker thread pool and can even lead to deadlocks. If you use asynchronous API, control the degree of concurrency, and avoid blocking threads, this problem can be avoided.
In iOS, Swift concurrency is “cooperative” and is predicated on a contract to always ensure forward progress. If you block an actor, the cooperative thread pool can be exhausted even more quickly, leading to deadlocks and other performance issues. See this answer.
FWIW, the caveat about “threads are expensive” is true, but in iOS, the thread pools (the GCD “worker thread pool” or Swift concurrency’s “cooperative thread pool”) abstract us away of the cost of spinning up new threads. So the computational expense of threads is not generally the operative concern. Other factors (e.g., those outlined above) usually are the more pressing concern.
So, in short, avoid blocking even a background thread where possible. And where you must block threads, make sure to mitigate against the degenerate scenarios.