swift-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Expert

Swift 专家指南

Expert guidance for Swift development including iOS/macOS apps, SwiftUI, Combine, async/await, and Swift 5.9+ features.
为Swift开发提供专业指导,包括iOS/macOS应用、SwiftUI、Combine、async/await及Swift 5.9+特性。

Core Concepts

核心概念

Modern Swift Features (5.9+)

现代Swift特性(5.9+)

  • Async/await concurrency
  • Actors for thread safety
  • Property wrappers
  • Result builders
  • Protocols and generics
  • Value types vs reference types
  • Automatic Reference Counting (ARC)
  • Macros (Swift 5.9+)
  • Async/await并发
  • 用于线程安全的Actors
  • 属性包装器
  • Result构建器
  • 协议与泛型
  • 值类型 vs 引用类型
  • 自动引用计数(ARC)
  • 宏(Swift 5.9+)

SwiftUI

SwiftUI

  • Declarative UI framework
  • State management
  • View composition
  • Layout system
  • Animations
  • Navigation
  • 声明式UI框架
  • 状态管理
  • 视图组合
  • 布局系统
  • 动画
  • 导航

Combine

Combine

  • Reactive programming
  • Publishers and subscribers
  • Operators
  • Error handling
  • 响应式编程
  • 发布者与订阅者
  • 操作符
  • 错误处理

Swift Syntax

Swift语法

Basics and Optionals

基础语法与可选类型

swift
// Variables and constants
var mutableValue = 42
let constantValue = 100

// Optionals
var optionalName: String? = "Alice"

// Optional binding
if let name = optionalName {
    print("Hello, \(name)")
}

// Optional chaining
let length = optionalName?.count

// Nil coalescing
let displayName = optionalName ?? "Unknown"

// Guard statement
func greet(person: String?) {
    guard let name = person else {
        print("No name provided")
        return
    }
    print("Hello, \(name)")
}
swift
// Variables and constants
var mutableValue = 42
let constantValue = 100

// Optionals
var optionalName: String? = "Alice"

// Optional binding
if let name = optionalName {
    print("Hello, \(name)")
}

// Optional chaining
let length = optionalName?.count

// Nil coalescing
let displayName = optionalName ?? "Unknown"

// Guard statement
func greet(person: String?) {
    guard let name = person else {
        print("No name provided")
        return
    }
    print("Hello, \(name)")
}

Functions and Closures

函数与闭包

swift
// Function with labeled parameters
func greet(person: String, from hometown: String) -> String {
    return "Hello \(person) from \(hometown)!"
}

// Closures
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0, +)

// Trailing closure
numbers.forEach { number in
    print(number)
}

// Capture values
func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    return {
        total += step
        return total
    }
}
swift
// Function with labeled parameters
func greet(person: String, from hometown: String) -> String {
    return "Hello \(person) from \(hometown)!"
}

// Closures
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0, +)

// Trailing closure
numbers.forEach { number in
    print(number)
}

// Capture values
func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    return {
        total += step
        return total
    }
}

Structs and Classes

结构体与类

swift
// Struct (value type, preferred)
struct User {
    let id: UUID
    var name: String
    var email: String

    // Computed property
    var displayName: String {
        name.isEmpty ? "Anonymous" : name
    }

    // Method
    mutating func updateEmail(_ newEmail: String) {
        email = newEmail
    }
}

// Class (reference type)
class ViewController {
    var title: String?
    weak var delegate: ViewControllerDelegate?

    init(title: String?) {
        self.title = title
    }

    deinit {
        print("Deallocated")
    }
}

// Protocol
protocol Identifiable {
    var id: UUID { get }
}

extension User: Identifiable {}
swift
// Struct (value type, preferred)
struct User {
    let id: UUID
    var name: String
    var email: String

    // Computed property
    var displayName: String {
        name.isEmpty ? "Anonymous" : name
    }

    // Method
    mutating func updateEmail(_ newEmail: String) {
        email = newEmail
    }
}

// Class (reference type)
class ViewController {
    var title: String?
    weak var delegate: ViewControllerDelegate?

    init(title: String?) {
        self.title = title
    }

    deinit {
        print("Deallocated")
    }
}

// Protocol
protocol Identifiable {
    var id: UUID { get }
}

extension User: Identifiable {}

Enums

枚举

swift
// Simple enum
enum Direction {
    case north, south, east, west
}

// Associated values
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

// Raw values
enum StatusCode: Int {
    case ok = 200
    case notFound = 404
    case serverError = 500
}

// Pattern matching
switch result {
case .success(let value):
    print("Success: \(value)")
case .failure(let error):
    print("Error: \(error)")
}
swift
// Simple enum
enum Direction {
    case north, south, east, west
}

// Associated values
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

// Raw values
enum StatusCode: Int {
    case ok = 200
    case notFound = 404
    case serverError = 500
}

// Pattern matching
switch result {
case .success(let value):
    print("Success: \(value)")
case .failure(let error):
    print("Error: \(error)")
}

Async/Await (Swift 5.5+)

Async/Await(Swift 5.5+)

swift
// Async function
func fetchUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Call async function
Task {
    do {
        let user = try await fetchUser(id: "123")
        print(user.name)
    } catch {
        print("Error: \(error)")
    }
}

// Parallel execution with async let
func loadUserData(id: String) async throws -> (User, [Post], [Comment]) {
    async let user = fetchUser(id: id)
    async let posts = fetchPosts(userId: id)
    async let comments = fetchComments(userId: id)

    return try await (user, posts, comments)
}

// Task groups for dynamic parallelism
func fetchMultipleUsers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}
swift
// Async function
func fetchUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Call async function
Task {
    do {
        let user = try await fetchUser(id: "123")
        print(user.name)
    } catch {
        print("Error: \(error)")
    }
}

// Parallel execution with async let
func loadUserData(id: String) async throws -> (User, [Post], [Comment]) {
    async let user = fetchUser(id: id)
    async let posts = fetchPosts(userId: id)
    async let comments = fetchComments(userId: id)

    return try await (user, posts, comments)
}

// Task groups for dynamic parallelism
func fetchMultipleUsers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

Actors (Thread Safety)

Actors(线程安全)

swift
actor BankAccount {
    private var balance: Double = 0

    func deposit(amount: Double) {
        balance += amount
    }

    func withdraw(amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }

    func getBalance() -> Double {
        balance
    }
}

// Usage (all access is async and serialized)
let account = BankAccount()
Task {
    await account.deposit(amount: 100)
    let balance = await account.getBalance()
    print(balance)
}
swift
actor BankAccount {
    private var balance: Double = 0

    func deposit(amount: Double) {
        balance += amount
    }

    func withdraw(amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }

    func getBalance() -> Double {
        balance
    }
}

// Usage (all access is async and serialized)
let account = BankAccount()
Task {
    await account.deposit(amount: 100)
    let balance = await account.getBalance()
    print(balance)
}

SwiftUI

SwiftUI

Basic Views

基础视图

swift
import SwiftUI

struct ContentView: View {
    @State private var name = ""
    @State private var count = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, \(name.isEmpty ? "World" : name)!")
                .font(.title)
                .foregroundColor(.blue)

            TextField("Enter name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()

            HStack {
                Button("Decrement") {
                    count -= 1
                }

                Text("\(count)")
                    .frame(minWidth: 50)

                Button("Increment") {
                    count += 1
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}
swift
import SwiftUI

struct ContentView: View {
    @State private var name = ""
    @State private var count = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, \(name.isEmpty ? "World" : name)!")
                .font(.title)
                .foregroundColor(.blue)

            TextField("Enter name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()

            HStack {
                Button("Decrement") {
                    count -= 1
                }

                Text("\(count)")
                    .frame(minWidth: 50)

                Button("Increment") {
                    count += 1
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

State Management

状态管理

swift
// @State - local view state
struct CounterView: View {
    @State private var count = 0

    var body: some View {
        Button("Count: \(count)") {
            count += 1
        }
    }
}

// @Binding - pass state reference
struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Setting", isOn: $isOn)
    }
}

// @ObservableObject - external state
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: Error?

    func fetchUser() async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await APIClient.shared.fetchUser()
        } catch {
            self.error = error
        }
    }
}

struct UserView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else if let user = viewModel.user {
                UserDetailView(user: user)
            } else if let error = viewModel.error {
                ErrorView(error: error)
            }
        }
        .task {
            await viewModel.fetchUser()
        }
    }
}

// @EnvironmentObject - app-wide state
@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
    }
}

struct SomeView: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        Text(appState.currentUser?.name ?? "Guest")
    }
}
swift
// @State - local view state
struct CounterView: View {
    @State private var count = 0

    var body: some View {
        Button("Count: \(count)") {
            count += 1
        }
    }
}

// @Binding - pass state reference
struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Setting", isOn: $isOn)
    }
}

// @ObservableObject - external state
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: Error?

    func fetchUser() async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await APIClient.shared.fetchUser()
        } catch {
            self.error = error
        }
    }
}

struct UserView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else if let user = viewModel.user {
                UserDetailView(user: user)
            } else if let error = viewModel.error {
                ErrorView(error: error)
            }
        }
        .task {
            await viewModel.fetchUser()
        }
    }
}

// @EnvironmentObject - app-wide state
@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
    }
}

struct SomeView: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        Text(appState.currentUser?.name ?? "Guest")
    }
}

Lists and Navigation

列表与导航

swift
struct PostListView: View {
    let posts: [Post]
    @State private var selectedPost: Post?

    var body: some View {
        NavigationStack {
            List(posts) { post in
                NavigationLink(value: post) {
                    VStack(alignment: .leading) {
                        Text(post.title)
                            .font(.headline)
                        Text(post.excerpt)
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle("Posts")
            .navigationDestination(for: Post.self) { post in
                PostDetailView(post: post)
            }
        }
    }
}
swift
struct PostListView: View {
    let posts: [Post]
    @State private var selectedPost: Post?

    var body: some View {
        NavigationStack {
            List(posts) { post in
                NavigationLink(value: post) {
                    VStack(alignment: .leading) {
                        Text(post.title)
                            .font(.headline)
                        Text(post.excerpt)
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle("Posts")
            .navigationDestination(for: Post.self) { post in
                PostDetailView(post: post)
            }
        }
    }
}

Custom Modifiers

自定义修饰器

swift
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardModifier())
    }
}

// Usage
Text("Hello")
    .cardStyle()
swift
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardModifier())
    }
}

// Usage
Text("Hello")
    .cardStyle()

Networking

网络请求

swift
actor APIClient {
    static let shared = APIClient()

    private let baseURL = URL(string: "https://api.example.com")!
    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
    }()

    func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
        let url = baseURL.appendingPathComponent(endpoint)
        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw APIError.invalidResponse
        }

        return try decoder.decode(T.self, from: data)
    }

    func post<T: Encodable, R: Decodable>(
        _ endpoint: String,
        body: T
    ) async throws -> R {
        var request = URLRequest(url: baseURL.appendingPathComponent(endpoint))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try decoder.decode(R.self, from: data)
    }
}

enum APIError: LocalizedError {
    case invalidResponse
    case decodingError

    var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Invalid server response"
        case .decodingError:
            return "Failed to decode response"
        }
    }
}
swift
actor APIClient {
    static let shared = APIClient()

    private let baseURL = URL(string: "https://api.example.com")!
    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
    }()

    func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
        let url = baseURL.appendingPathComponent(endpoint)
        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw APIError.invalidResponse
        }

        return try decoder.decode(T.self, from: data)
    }

    func post<T: Encodable, R: Decodable>(
        _ endpoint: String,
        body: T
    ) async throws -> R {
        var request = URLRequest(url: baseURL.appendingPathComponent(endpoint))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try decoder.decode(R.self, from: data)
    }
}

enum APIError: LocalizedError {
    case invalidResponse
    case decodingError

    var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Invalid server response"
        case .decodingError:
            return "Failed to decode response"
        }
    }
}

Combine Framework

Combine框架

swift
import Combine

class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [SearchResult] = []
    @Published var isLoading = false

    private var cancellables = Set<AnyCancellable>()

    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    private func performSearch(_ text: String) {
        guard !text.isEmpty else {
            results = []
            return
        }

        isLoading = true

        Task {
            do {
                let searchResults: [SearchResult] = try await APIClient.shared
                    .fetch("search?q=\(text)")
                await MainActor.run {
                    self.results = searchResults
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.isLoading = false
                }
            }
        }
    }
}
swift
import Combine

class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [SearchResult] = []
    @Published var isLoading = false

    private var cancellables = Set<AnyCancellable>()

    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    private func performSearch(_ text: String) {
        guard !text.isEmpty else {
            results = []
            return
        }

        isLoading = true

        Task {
            do {
                let searchResults: [SearchResult] = try await APIClient.shared
                    .fetch("search?q=\(text)")
                await MainActor.run {
                    self.results = searchResults
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.isLoading = false
                }
            }
        }
    }
}

Testing

测试

swift
import XCTest
@testable import MyApp

final class UserViewModelTests: XCTestCase {
    var viewModel: UserViewModel!
    var mockAPIClient: MockAPIClient!

    override func setUp() {
        super.setUp()
        mockAPIClient = MockAPIClient()
        viewModel = UserViewModel(apiClient: mockAPIClient)
    }

    func testFetchUserSuccess() async throws {
        // Given
        let expectedUser = User(id: UUID(), name: "Alice", email: "alice@example.com")
        mockAPIClient.userToReturn = expectedUser

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertEqual(viewModel.user?.name, "Alice")
        XCTAssertNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }

    func testFetchUserFailure() async {
        // Given
        mockAPIClient.shouldFail = true

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertNil(viewModel.user)
        XCTAssertNotNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }
}

// Mock
class MockAPIClient {
    var userToReturn: User?
    var shouldFail = false

    func fetchUser() async throws -> User {
        if shouldFail {
            throw APIError.invalidResponse
        }
        return userToReturn ?? User(id: UUID(), name: "Test", email: "test@example.com")
    }
}
swift
import XCTest
@testable import MyApp

final class UserViewModelTests: XCTestCase {
    var viewModel: UserViewModel!
    var mockAPIClient: MockAPIClient!

    override func setUp() {
        super.setUp()
        mockAPIClient = MockAPIClient()
        viewModel = UserViewModel(apiClient: mockAPIClient)
    }

    func testFetchUserSuccess() async throws {
        // Given
        let expectedUser = User(id: UUID(), name: "Alice", email: "alice@example.com")
        mockAPIClient.userToReturn = expectedUser

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertEqual(viewModel.user?.name, "Alice")
        XCTAssertNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }

    func testFetchUserFailure() async {
        // Given
        mockAPIClient.shouldFail = true

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertNil(viewModel.user)
        XCTAssertNotNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }
}

// Mock
class MockAPIClient {
    var userToReturn: User?
    var shouldFail = false

    func fetchUser() async throws -> User {
        if shouldFail {
            throw APIError.invalidResponse
        }
        return userToReturn ?? User(id: UUID(), name: "Test", email: "test@example.com")
    }
}

Best Practices

最佳实践

Code Organization

代码组织

  • Use MVVM pattern for SwiftUI
  • Separate business logic from views
  • Use dependency injection
  • Keep views small and composable
  • 为SwiftUI使用MVVM模式
  • 将业务逻辑与视图分离
  • 使用依赖注入
  • 保持视图小巧且可组合

Memory Management

内存管理

  • Understand ARC (Automatic Reference Counting)
  • Use weak references for delegates
  • Break retain cycles with [weak self] or [unowned self]
  • Use actors for mutable shared state
  • 理解自动引用计数(ARC)
  • 为代理使用弱引用
  • 使用[weak self]或[unowned self]打破循环引用
  • 为可变共享状态使用Actors

Performance

性能优化

  • Use lazy loading where appropriate
  • Avoid unnecessary view updates
  • Profile with Instruments
  • Use value types (structs) by default
  • 合理使用懒加载
  • 避免不必要的视图更新
  • 使用Instruments进行性能分析
  • 默认使用值类型(结构体)

Swift Concurrency

Swift并发

  • Prefer async/await over completion handlers
  • Use actors for thread-safe mutable state
  • Use @MainActor for UI updates
  • Avoid blocking the main thread
  • 优先使用async/await而非完成处理程序
  • 为线程安全的可变状态使用Actors
  • 为UI更新使用@MainActor
  • 避免阻塞主线程

Anti-Patterns to Avoid

需避免的反模式

Force unwrapping: Use optional binding instead ❌ Massive view controllers: Extract logic to view models ❌ Strong reference cycles: Use weak/unowned references ❌ Blocking main thread: Use async/await ❌ Ignoring memory warnings: Handle memory pressure ❌ Not using guard: Use guard for early exits ❌ Implicit unwrapping: Prefer explicit optionals
强制解包:改用可选绑定 ❌ 巨型视图控制器:将逻辑提取到视图模型 ❌ 强引用循环:使用弱引用/无主引用 ❌ 阻塞主线程:使用async/await ❌ 忽略内存警告:处理内存压力 ❌ 不使用guard:使用guard提前退出 ❌ 隐式解包:优先使用显式可选类型

Resources

资源