swift-expert
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift 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
资源
- Swift Documentation: https://swift.org/documentation/
- SwiftUI Tutorials: https://developer.apple.com/tutorials/swiftui
- WWDC Sessions: https://developer.apple.com/videos/
- Swift by Sundell: https://www.swiftbysundell.com/
- Hacking with Swift: https://www.hackingwithswift.com/
- Swift文档:https://swift.org/documentation/
- SwiftUI教程:https://developer.apple.com/tutorials/swiftui
- WWDC大会视频:https://developer.apple.com/videos/
- Swift by Sundell:https://www.swiftbysundell.com/
- Hacking with Swift:https://www.hackingwithswift.com/