Hello All Swift enthusiasts.
Today, let us dive into the amazing world of concurrency in Swift and look at how the use of async/await has made our job easy as an iOS Developers.
The Concurrency Problem
Concurrency is like juggling multiple tasks at once – it’s all about managing and executing tasks simultaneously. In the realm of Swift, handling concurrency traditionally involved closures, but let’s be real, closures could be a bit unwieldy. Enter async/await – the new sheriff in town, making concurrent programming in Swift more readable and enjoyable.
The Saviour : Closures
Before we get our hands dirty with async/await, let’s take some time to appreciate closures. They served us well in handling asynchronous tasks, but they had their quirks. Nested closures could resemble a labyrinth, making code readability an uphill battle. Here’s a quick peek at how closures used to handle asynchronous operations:
func transferMoney(completion: @escaping (Bool) -> Void) {
// Async task
DispatchQueue.global().async {
// Money transfer logic
let success = performMoneyTransfer()
// Callback
DispatchQueue.main.async {
completion(success)
}
}
}
It gets the job done, but it’s not the most elegant solution. Enter async/await – the syntax superhero that swoops in to save the day!
Enter the Hero: Async/Await
Async/await is a game-changer, simplifying asynchronous code and making it look like synchronous code. Let’s rewrite our money transfer function using this new duo:
func transferMoney() async -> Bool {
// Money transfer logic
let success = await performMoneyTransfer()
return success
}
Clean, concise, and easy to follow – that’s the beauty of async/await. But hold your horses; let’s not get too carried away. It’s essential to recognize that async/await isn’t a silver bullet; it has its own set of challenges.
The Dark Side: Drawbacks of Async/Await
- Learning Curve: As with any new concept, there’s a learning curve. Developers need to familiarize themselves with the async/await syntax and understand its intricacies.
- Tooling Support: While async/await is now a part of Swift, full tooling support might still be catching up. Debugging asynchronous code could be a bit trickier than synchronous code.
- Compatibility Issues: If you’re working on a project that supports older Swift versions, you might run into compatibility issues. Async/await is available starting from Swift 5.5.
Now, let’s put our newfound knowledge to the test with a real-world example – a banking system with user transactions.
Example: Banking System with Async/Await
struct User {
var balance: Double = 1000.0
}
func performMoneyTransfer(amount: Double, from sender: inout User, to receiver: inout User) async -> Bool {
// Async task simulating money transfer
await Task.sleep(1 * 1_000_000_000) // Simulate some processing time
if sender.balance >= amount {
sender.balance -= amount
receiver.balance += amount
return true
} else {
return false
}
}
// Example usage
async {
var user1 = User()
var user2 = User()
let success = await performMoneyTransfer(amount: 500.0, from: &user1, to: &user2)
if success {
print("Money transfer successful!")
print("User 1 balance: \(user1.balance), User 2 balance: \(user2.balance)")
} else {
print("Insufficient funds for money transfer.")
}
}
There you have it – a simple banking system using async/await in Swift. We’ve come a long way from nested closures to this elegant and readable async/await syntax.
Detailed Example
import Foundation
// MARK: - Enums
enum TransactionType {
case deposit
case withdrawal
case transfer
}
// MARK: - Property Wrapper
@propertyWrapper
struct ValidAmount {
private var value: Double
var wrappedValue: Double {
get { value }
set {
if newValue >= 0 {
value = newValue
} else {
print("Invalid amount. Amount should be non-negative.")
}
}
}
init(initialValue: Double) {
self.value = initialValue
}
}
// MARK: - Model
class BankAccount {
var accountHolder: String
@ValidAmount(initialValue: 0.0) var balance: Double
init(accountHolder: String) {
self.accountHolder = accountHolder
}
func performTransaction(type: TransactionType, amount: Double) async throws {
switch type {
case .deposit:
balance += amount
print("Deposit of \(amount) successful. New balance: \(balance)")
case .withdrawal:
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
print("Withdrawal of \(amount) successful. New balance: \(balance)")
case .transfer:
throw BankError.invalidTransaction
}
}
}
class SavingsAccount: BankAccount {
var interestRate: Double
init(accountHolder: String, interestRate: Double) {
self.interestRate = interestRate
super.init(accountHolder: accountHolder)
}
override func performTransaction(type: TransactionType, amount: Double) async throws {
try await super.performTransaction(type: type, amount: amount)
switch type {
case .deposit:
applyInterest()
default:
break
}
}
private func applyInterest() {
let interest = balance * interestRate
balance += interest
print("Interest applied. New balance: \(balance)")
}
}
// MARK: - Error
enum BankError: Error {
case insufficientFunds
case invalidTransaction
}
// MARK: - Example Usage
async {
do {
var userAccount = BankAccount(accountHolder: "John Doe")
try await userAccount.performTransaction(type: .deposit, amount: 1000.0)
try await userAccount.performTransaction(type: .withdrawal, amount: 500.0)
var savingsAccount = SavingsAccount(accountHolder: "Jane Doe", interestRate: 0.05)
try await savingsAccount.performTransaction(type: .deposit, amount: 2000.0)
try await savingsAccount.performTransaction(type: .withdrawal, amount: 300.0)
} catch {
print("Error: \(error)")
}
}
- We have two types of transactions:
TransactionType.deposit
,TransactionType.withdrawal
, andTransactionType.transfer
. - The
@ValidAmount
property wrapper ensures that the amount is non-negative. - The
BankAccount
class handles basic banking operations like deposits and withdrawals, and theSavingsAccount
class inherits from it, adding the ability to apply interest on deposits. - Asynchronous tasks are performed using the
async
andawait
keywords. - Error handling is done through Swift’s
throws
andError
protocol, with specific error cases defined in theBankError
enum.
In conclusion
Async and await is a powerful tool in Swift’s arsenal, providing a cleaner and more readable way to handle asynchronous tasks. While it’s not without its challenges, the benefits it brings to the table make it a valuable addition to the Swift developer’s toolkit. So, go ahead, embrace the concurrency magic, and let your code shine!
Add comment