Swift Concurrency Series 01|The reason why Swift introduces async/await
It is Swift's attempt to upgrade concurrency from "depending on convention" to "language capability"
When you see async/await for the first time, you will understand it as an “upgraded version of callback writing”.
This understanding is only half correct.
If you just want to write shorter code and more synchronous style, then other languages have already done similar things. What Swift really wants to solve is that the old asynchronous model has become increasingly difficult to support the complexity of modern iOS projects.
In the past, you might only need to process a button click to send a request, but now an ordinary page may involve:
- Load multiple resources in parallel on the first screen
- Cancel old tasks when leaving the page
- Local cache and remote request compete for the same state
- Automatically refresh token after login status expires
- Main thread UI updates and background processing occur interspersed
Once the business reaches this complexity, the asynchronous problem is no longer just a writing problem, but a system design problem. The significance of async/await is precisely that it advances this type of problem from “library-level habits” to “language-level rules”.
1. The real problem with the old model is that the control flow is broken
Everyone likes to use “callback hell” to explain the problems of the old asynchronous writing method, but if you only stop at “indentation is too deep”, you still miss the point.
The real trouble with callbacks is that the ** business is originally a line, but the code is split into many discrete fragments. **
For example, a very common initialization process:
- Pull current user information
- Determine the home page module based on user permissions
- Pull the homepage data again
- If it fails, record the hidden points
- Finally return to the main thread to update the UI
This is a very clearly sequenced link in my mind. But in the completion era, this link is often split into:
- a request closure
- A permission judgment closure
- Several layers of error handling
- A main thread switch
- Several early return branches
As the code gets longer, it becomes difficult to explain at a glance:
- What is the main process?
- Which branches will be broken
- Who should take the blame when a step fails?
- Should you continue after leaving the page?
The real difficulty in maintaining asynchronous logic is that the intention and expression are out of touch.
2. The biggest problem with completion is that it leaves too many key semantics to the convention
completion is not a bad thing. Many of the underlying APIs are still very useful today, and are still suitable in certain scenarios that require multiple callbacks, streaming output, and bridging older systems.
The problem is that when a system relies heavily on completion, a lot of the important semantics are just “team habits” rather than language rules.
For example, the following things are often understood by default in the completion model:
- Will this callback be called once or multiple times?
- Are success and failure strictly mutually exclusive?
- In which thread the callback will return?
- Whether the caller has the ability to cancel
- If the object is released midway, should the results continue to be delivered?
You will find that these are the core issues of concurrent systems.
Once these semantics are maintained by documentation, naming, and experience, the larger the project, the easier it is for the semantics to diverge. The most painful thing in the end is usually the inability to stably reason about how a piece of asynchronous code will run.
3. Swift introduces async/await, which is essentially tightening the rules of asynchronous code.
The value of async/await is not just about:
fetchUser { result in
...
}
Replace with:
let user = try await fetchUser()
More importantly, it brings back many things that were originally scattered in the convention back to the language level.
For example now:
- A
asyncfunction has a clear return point - Failure is propagated via
throwrather than by a different style of result callback awaitexplicitly tells that this is the pause point- The asynchronous call chain can be read along without jumping between multiple closures.
The significance of this matter is not “looking good” but “the rules are tighter”.
The real fear of complex systems is always loose rules. Once the rules are loosened, the code becomes increasingly dependent on “this person just happens to understand the context.”
4. It improves not only readability, but also reasonability
A common situation is: I can understand completion, why do I have to learn async/await?
The question is not whether you can understand it today, but:
- Can you still understand quickly after three months?
- When colleagues take over, can they deduce the process based on the code itself?
- When a bug occurs, is it possible to walk from the entrance to the exit?
This is the difference between “readability” and “reasonableness”.
Readability is “Is this code pleasing to my eyes today?” Reasonability is “Can I judge based on this code: how failure is transmitted, how cancellation takes effect, and at which level the status is changed.”
For example, the following problems are often very difficult to pursue in the old model:
- After the user leaves the page, does this request chain still exist?
- When the third step fails, where will the final page status stop?
- When two asynchronous results come back at the same time, who is qualified to write the UI?
async/await certainly won’t automatically answer these questions for the team, but it at least allows these questions to be placed in a clearer process structure rather than hidden in fragmented closures.
5. This matter is becoming more and more critical in iOS projects
One of the biggest changes in the iOS project today is that “asynchrony has changed from a marginal capability to a backbone capability.”
In the past, asynchronous was more about “click and send a request.” Now asynchronously directly participates in the page life cycle, state machine, cache layer, permission system, hidden point system, and even participates in the design of the response speed of the entire product.
A common situation for a real business page is:
- There will be a first screen request as soon as the page is opened.
- Some modules use local cache to evade
- Some requests rely on user profiles or experimental configurations
- Certain operations require reentrancy prevention
- Some results must be returned to the main thread to safely change the state
Under this premise, if the asynchronous system is still just “everyone writes his own completion”, the code will soon slip from “can work” to “no one dares to change”.
So what async/await really catches is that asynchronous has become the default working method in modern apps.
6. It is the entrance to Swift’s entire concurrency model.
If you only look at this grammatical feature, async/await seems to be just a better asynchronous expression.
But if you look at Swift Concurrency as a whole, it is actually paving the way for subsequent capabilities:
Task: Clarify task boundaries and life cycleMainActor: Constrain execution semantics of UI related statesActor: Isolate shared mutable state- Structured concurrency: incorporate subtasks into the parent task life cycle
In other words, Swift is not just trying to provide a more convenient asynchronous API, it is trying to turn “concurrency” from a piecemeal technique into a set of composable, reasonable, and language-checkable models.
This also shows that async/await cannot be regarded as just “easier to write”. What’s really connected behind it is a whole new set of concurrency design philosophies.
7. It does not automatically solve all problems, but it changes the nature of the problem.
It must also be made clear here: with async/await, concurrency bugs will not automatically disappear.
It doesn’t solve:
- Wrong design of state boundaries
- Old tasks overwrite new results
- A ViewModel takes on too many responsibilities at the same time
- The mission cancellation strategy is not thought through at all
But it changes one very important thing: Many problems are no longer “because the expression is too confusing and I can’t even see what the problem looks like”, but “the problem has been clearly expressed.”
This may sound like a step back, but it’s actually a giant step forward. Because only when the problem becomes expressible can debugging, review, and refactoring be effective.
8. Conclusion: Swift introduces async/await not to write less closures
To put this in a shorter way, I would say:
Swift introduces
async/awaitto make asynchronous code understandable, reasonable, and maintainable again.
So it is the first step in upgrading the concurrency model.
Once you understand this, when you look at Task, Actor, and MainActor later, you won’t regard them as scattered syntax points, but you will understand that what Swift is doing is a bigger thing:
**Reclassify concurrency from “empirical skills” to “language rules”. **
读完之后,下一步看什么
如果还想继续了解,可以从下面几个方向接着读。