| name | swift-expert |
| description | Swift development principles and decision-making. Swift 5.9+, SwiftUI, Swift Concurrency (async/await, actors). |
Swift Expert - Modern Swift 5.9+
Swift development principles and decision-making.
Learn to THINK, not copy-paste patterns.
⚠️ How to Use This Skill
This skill teaches decision-making principles for Swift development.
- ASK user for platform (iOS, macOS, Linux, Server)
- Use Swift Concurrency (async/await), not completion handlers
- Don't mix UIKit patterns in SwiftUI apps
1. Platform Selection
Decision Tree
What are you building?
│
├── iOS/tvOS/watchOS App
│ ├── SwiftUI (modern UI - RECOMMENDED)
│ └── UIKit (legacy, complex animations)
│
├── macOS Desktop App
│ ├── SwiftUI + AppKit (best of both)
│ └── AppKit (full control)
│
├── Cross-Platform CLI Tool
│ └── Swift Package + swift build
│
├── Server-Side API
│ ├── Vapor (most popular)
│ ├── Hummingbird (lighter)
│ └── Fast (async/)
│
├── Web (Swift->WASM)
│ └── SwiftWasm (experimental)
│
└── Library/Framework
└── Swift Package Manager
Comparison Table
| Factor | SwiftUI | UIKit | SwiftUI + UIKit |
|---|
| Learning Curve | Lower | Higher | Mixed |
| Declarative | ✅ Yes | ❌ No | ✅ Partial |
| Reuse across platforms | ✅ Best | ❌ No | ✅ Good |
| Animations | Built-in | Manual | Via UIViewRepresentable |
2. Swift Concurrency (Modern)
async/await (Swift 5.5+)
func fetchUser(id: Int, completion: @escaping (User?) -> Void) {
completion(user)
}
fetchUser(id: 1) { user in
print(user?.name)
}
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
Task {
do {
let user = try await fetchUser(id: 1)
print(user.name)
} catch {
print(error)
}
}
Actors (Thread Safety)
class BankAccount {
private var balance: Double = 0
private let lock = NSLock()
func deposit(_ amount: Double) {
lock.lock()
balance += amount
lock.unlock()
}
}
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) -> Double {
if balance >= amount {
balance -= amount
return amount
}
return 0
}
}
let account = BankAccount()
await account.deposit(100)
AsyncSequence
func numbers() -> AsyncStream<Int> {
AsyncStream { continuation in
Task {
for i in 1...5 {
continuation.yield(i)
try await Task.sleep(for: .milliseconds(500))
}
continuation.finish()
}
}
}
Task {
for await number in numbers() {
print(number)
}
}
3. SwiftUI Patterns
State Management
class UserViewModel: ObservableObject {
@Published var users: [User] = []
func loadUsers() async {
}
}
struct UserListView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
List(viewModel.users) { user in
Text(user.name)
}
}
}
struct EditView: View {
@Binding var user: User
var body: some View {
TextField("Name", text: $user.name)
}
}
Dependency Injection with @Environment
struct DatabaseKey: EnvironmentKey {
static var defaultValue: Database = {
fatalError("Set Database in app")
}()
}
ContentView()
.environment(\.database, Database())
struct UserListView: View {
@Environment(\.database) var database
var body: some View {
}
}
4. Server-Side Swift
Vapor Routing
import Vapor
func configureRoutes(_ app: Application) throws {
app.get("users") { req async throws -> [User] in
try await User.query.all()
}
app.get("users", ":id") { req async throws -> User in
guard let user = try await User.find(
req.parameters.get("id"),
on: req.db
) else {
throw Abort(.notFound)
}
return user
}
app.post("users") { req async throws -> User in
let user = try req.content.decode(User.self)
try await user.save(on: req.db)
return user
}
}
Comparison
| Framework | Best For | Performance |
|---|
| Vapor | Full-featured APIs | Good |
| Hummingbird | Lightweight, high performance | Better |
| Fast | io_uring, async-first | Best |
5. Value Types & Structs
Choose Wisely
struct User: Identifiable, Codable {
let id: UUID
var name: String
var email: String
}
class UserManager: ObservableObject {
@Published var currentUser: User?
}
actor Cache {
private var store: [String: Data] = [:]
func get(_ key: String) -> Data? {
store[key]
}
func set(_ key: String, value: Data) {
store[key] = value
}
}
6. Error Handling
Result Type & throws
enum MyError: Error {
case notFound
case invalidInput(String)
case network(Error)
}
func fetchUser(id: Int) async throws -> User {
guard id > 0 else {
throw MyError.invalidInput("ID must be positive")
}
do {
let user = try await api.call(id)
return user
} catch let error as MyError {
throw error
} catch {
throw MyError.network(error)
}
}
let result = await Result { try await fetchUser(id: 1) }
result.get()
result.map { user in user.name }
.flatMap { }
7. Testing Patterns
XCTest + @MainActor
import XCTest
@MainActor
class UserViewModelTests: XCTestCase {
var viewModel: UserViewModel!
override func setUp() {
viewModel = UserViewModel()
}
func test_loadUsers_updatesState() async {
let mockService = MockUserService()
await viewModel.loadUsers()
XCTAssertFalse(viewModel.users.isEmpty)
}
}
Testing Tools
| Tool | Purpose |
|---|
| XCTest | Built-in testing |
| Quick/Nimble | BDD style (more readable) |
| XCTAsyncUtils | Async testing utilities |
| SnapshotTesting | UI snapshot tests |
8. Package Management
Swift Package Manager
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
.package(url: "https://github.com/SwiftUIX/SwiftUIX.git", branch: "main"),
]
9. Decision Checklist
Before implementing:
❌ Anti-Patterns to Avoid
❌ DON'T:
- Use completion handlers when async/await exists
- Use class for everything (over-use reference types)
- Use ! for force unwrapping (use guard/if let)
- Use CocoaPods when SPM exists
- Skip @MainActor for UI code
✅ DO:
- Use Swift Concurrency (async/await everywhere)
- Use actors for thread-safe state
- Use structs + SwiftUI whenever possible
- Use async/await + AsyncSequence for streams
- Use @Environment for DI in SwiftUI
- Use Swift Package Manager
Remember: Swift is designed for safety and concurrency.
Use Swift Concurrency natively, not completion handlers.
Think in Swift.