# คำถามสัมภาษณ์ Swift Structured Concurrency: async/await, TaskGroup, Actors > คำถามสัมภาษณ์เชิงเทคนิค Swift Structured Concurrency: async/await, TaskGroup, actors และรูปแบบ concurrency สำหรับ iOS 2026 - Published: 2026-02-26 - Updated: 2026-04-29 - Author: SharpSkill - Tags: swift, concurrency, async-await, actors, interview - Reading time: 12 min --- Structured Concurrency ที่เปิดตัวใน Swift 5.5 ได้ปฏิวัติการเขียนโปรแกรมแบบอะซิงโครนัสบน iOS ผู้สรรหาบุคลากรในปัจจุบันทดสอบความเชี่ยวชาญใน async/await, TaskGroup และ actors ในระหว่างการสัมภาษณ์เชิงเทคนิค ต่อไปนี้คือคำถามสำคัญและคำตอบที่คาดหวังเพื่อสร้างความโดดเด่นในการสัมภาษณ์ > **ทักษะหลักที่ทดสอบในการสัมภาษณ์** > > ผู้สรรหาประเมินสามทักษะ: ความเข้าใจในแนวคิดพื้นฐาน (async/await, Task), ความเชี่ยวชาญในรูปแบบ concurrency (TaskGroup, actor isolation) และความสามารถในการวินิจฉัยข้อผิดพลาดที่พบบ่อย (data races, deadlocks) ## ความแตกต่างระหว่าง async/await และ DispatchQueue คืออะไร? **คำตอบที่คาดหวัง**: async/await ให้ structured concurrency ด้วยโค้ดเชิงลำดับที่อ่านง่าย ในขณะที่ DispatchQueue ใช้ callback และอาจนำไปสู่สิ่งที่เรียกว่า "callback hell" Swift จัดการเธรดโดยอัตโนมัติด้วย async/await ```swift // NetworkService.swift // Comparison: async/await vs DispatchQueue // ❌ Old style: DispatchQueue with callbacks func fetchUserOld(id: Int, completion: @escaping (Result) -> Void) { DispatchQueue.global().async { // Simulated network call let result = self.performNetworkRequest(id: id) DispatchQueue.main.async { completion(result) } } } // ✅ New style: async/await more readable func fetchUser(id: Int) async throws -> User { // Swift runtime handles threads automatically // No need to manually switch between queues return try await performNetworkRequest(id: id) } ``` **ประเด็นสำคัญ**: async/await ขจัด callback pyramid ลดข้อผิดพลาดของเธรด (ไม่ต้อง `DispatchQueue.main.async`) และอนุญาตให้ Swift runtime เพิ่มประสิทธิภาพการดำเนินการในคอร์ CPU ที่มี > **ข้อได้เปรียบด้านประสิทธิภาพ** > > Swift runtime ใช้ thread pool ที่ปรับให้เหมาะสมซึ่งหลีกเลี่ยงการสร้างเธรดมากเกินไป ต่างจาก DispatchQueue ที่แต่ละ `.async` อาจสร้างเธรดใหม่ async/await นำเธรดที่มีอยู่กลับมาใช้ใหม่อย่างชาญฉลาด ## จะจัดการการดำเนินการอะซิงโครนัสหลายรายการแบบขนานได้อย่างไร? **คำตอบที่คาดหวัง**: ใช้ `async let` สำหรับงาน 2-3 งานที่เรียบง่าย หรือ `TaskGroup` สำหรับจำนวนงานขนานแบบไดนามิกพร้อมการรวบรวมผลลัพธ์ ```swift // DataFetcher.swift // Parallel async operations strategies struct DataFetcher { // Strategy 1: async let for fixed tasks (2-4 operations) func loadDashboard() async throws -> Dashboard { // Launch 3 requests in parallel async let user = fetchUser() async let posts = fetchPosts() async let notifications = fetchNotifications() // Wait for results (parallel, not sequential) let (userData, postsData, notificationsData) = try await (user, posts, notifications) return Dashboard(user: userData, posts: postsData, notifications: notificationsData) } // Strategy 2: TaskGroup for dynamic number of tasks func downloadImages(urls: [URL]) async throws -> [UIImage] { // TaskGroup allows managing N tasks with result collection try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in // Launch one task per URL for (index, url) in urls.enumerated() { group.addTask { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw ImageError.invalidData } return (index, image) // Return index to preserve order } } // Collect results in order var images = [UIImage?](repeating: nil, count: urls.count) for try await (index, image) in group { images[index] = image } return images.compactMap { $0 } } } } ``` **ข้อผิดพลาดทั่วไป**: การใช้ `await` ตามลำดับแทนที่จะใช้ `async let` ซึ่งทำให้การเรียกขนานกัน `let user = await fetchUser(); let posts = await fetchPosts()` ดำเนินการตามลำดับ (ช้า) ในขณะที่ `async let` เปิดทั้งสองพร้อมกัน ## Actor คืออะไรและทำไมต้องใช้? **คำตอบที่คาดหวัง**: Actor เป็นประเภทที่ปกป้องสถานะที่เปลี่ยนแปลงได้จาก data races โดยรับประกันการเข้าถึงตามลำดับ ใช้แทน lock แบบแมนนวล (NSLock, DispatchQueue) เพื่อรักษาความปลอดภัยของการเข้าถึงพร้อมกัน ```swift // CacheManager.swift // Actor for thread-safe state management // ❌ Classic class: data race risk class UnsafeCache { private var cache: [String: Data] = [:] // Not thread-safe! func store(_ data: Data, for key: String) { cache[key] = data // ⚠️ Race condition with concurrent access } } // ✅ Actor: automatic protection against races actor SafeCache { private var cache: [String: Data] = [:] // Sequential access guaranteed by actor isolation func store(_ data: Data, for key: String) { cache[key] = data // ✅ Thread-safe automatically } func retrieve(for key: String) -> Data? { return cache[key] // ✅ Protected read } // Internal synchronous method (no await needed) nonisolated func clearAll() async { // nonisolated allows calling from any context await self.clear() } private func clear() { cache.removeAll() } } // Usage: await required to access actor let cache = SafeCache() await cache.store(data, for: "user_123") // Await mandatory let cachedData = await cache.retrieve(for: "user_123") ``` **ประเด็นสำคัญ**: Actor รับประกันว่ามีเธรดเดียวเข้าถึงสถานะของมันในเวลาเดียวกัน คอมไพเลอร์บังคับใช้ `await` สำหรับการเรียกภายนอก ทำให้จุดพักที่อาจเกิดขึ้นชัดเจน > **กับดัก MainActor** > > `@MainActor` เป็น actor ระดับโลกสำหรับการดำเนินการ UI การทำเครื่องหมายคลาสด้วย `@MainActor` บังคับให้เมธอดทั้งหมดของมันทำงานบน main thread ระวังการเรียกที่บล็อกซึ่งอาจทำให้ส่วนติดต่อค้าง ## จะจัดการข้อผิดพลาดใน TaskGroup ได้อย่างไร? **คำตอบที่คาดหวัง**: `withThrowingTaskGroup` ส่งต่อข้อผิดพลาดแรกที่พบและยกเลิกงานที่เหลืออัตโนมัติ ในการรวบรวมข้อผิดพลาดทั้งหมด ใช้ `Result` ใน TaskGroup ```swift // BatchProcessor.swift // Error handling strategies in TaskGroup struct BatchProcessor { // Strategy 1: First error propagation (fail-fast) func processItemsFastFail(items: [Item]) async throws -> [ProcessedItem] { try await withThrowingTaskGroup(of: ProcessedItem.self) { group in for item in items { group.addTask { // If one task throws, group cancels others try await self.process(item) } } // Collect results until first error var results: [ProcessedItem] = [] for try await result in group { results.append(result) } return results } // ⚠️ If one task fails, others are cancelled } // Strategy 2: Collect all errors (resilience) func processItemsResilient(items: [Item]) async -> ([ProcessedItem], [Error]) { await withTaskGroup(of: Result.self) { group in for item in items { group.addTask { // Wrap in Result to capture errors do { let result = try await self.process(item) return .success(result) } catch { return .failure(error) } } } // Separate successes/failures var successes: [ProcessedItem] = [] var errors: [Error] = [] for await result in group { switch result { case .success(let item): successes.append(item) case .failure(let error): errors.append(error) } } return (successes, errors) } } private func process(_ item: Item) async throws -> ProcessedItem { // Processing with possible error try await Task.sleep(nanoseconds: 100_000_000) return ProcessedItem(from: item) } } ``` **ประเด็นสำคัญ**: `withThrowingTaskGroup` หยุดที่ข้อผิดพลาดแรก (มีประโยชน์สำหรับการดำเนินการแบบอะตอมิก) ในขณะที่ `withTaskGroup` + `Result` อนุญาตให้ดำเนินการต่อแม้จะมีข้อผิดพลาด (มีประโยชน์สำหรับการประมวลผลแบบ batch) ## ความแตกต่างระหว่าง Task, Task.detached และ async let คืออะไร? **คำตอบที่คาดหวัง**: `Task` สืบทอดบริบทของ parent (ลำดับความสำคัญ, actor isolation), `Task.detached` สร้างงานอิสระโดยไม่สืบทอด และ `async let` สร้าง child task ที่รออัตโนมัติเมื่อจบ scope ```swift // TaskLifecycle.swift // Understanding Task creation patterns @MainActor class ViewModel { var isLoading = false // Scenario 1: Task inherits context (@MainActor here) func loadDataWithTask() { Task { // ✅ Inherits @MainActor from parent // No need for await MainActor.run self.isLoading = true let data = try await fetchData() self.isLoading = false // ✅ Always on MainActor } } // Scenario 2: Task.detached creates independent task func loadDataDetached() { Task.detached { // ⚠️ Does NOT inherit @MainActor let data = try await self.fetchData() // ❌ Error: isLoading not directly accessible // await MainActor.run { // self.isLoading = false // } } } // Scenario 3: async let creates structured child task func loadMultipleData() async throws { // async let tasks are bound to current scope async let users = fetchUsers() async let posts = fetchPosts() // ⚠️ If leaving function before await, compilation error let (usersData, postsData) = try await (users, posts) // async let tasks automatically cancelled // if exiting scope (e.g., throw before await) } private func fetchData() async throws -> Data { try await URLSession.shared.data(from: URL(string: "https://api.example.com")!).0 } private func fetchUsers() async throws -> [User] { [] } private func fetchPosts() async throws -> [Post] { [] } } ``` **กรณีการใช้งาน**: - `Task`: การดำเนินการที่ผูกกับบริบทปัจจุบัน (เช่น การอัปเดต UI จาก ViewModel) - `Task.detached`: งานเบื้องหลังที่อิสระ (เช่น log, analytics) - `async let`: การดำเนินการแบบขนานที่ต้องการผลลัพธ์ใน scope ปัจจุบัน ## จะใช้งาน timeout บนการดำเนินการอะซิงโครนัสได้อย่างไร? **คำตอบที่คาดหวัง**: ใช้ `Task.sleep` ในการแข่งขันระหว่างงานหลักและงาน timeout ด้วย `withThrowingTaskGroup` หรือสร้างยูทิลิตี้ `withTimeout` ```swift // AsyncTimeout.swift // Timeout implementation for async operations enum TimeoutError: Error { case timedOut } // Generic utility to add timeout func withTimeout( seconds: TimeInterval, operation: @escaping () async throws -> T ) async throws -> T { try await withThrowingTaskGroup(of: T.self) { group in // Task 1: main operation group.addTask { try await operation() } // Task 2: timeout group.addTask { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) throw TimeoutError.timedOut } // First task to finish wins guard let result = try await group.next() else { throw TimeoutError.timedOut } // Cancel losing task (important for cleanup) group.cancelAll() return result } } // Usage example struct NetworkService { func fetchUserWithTimeout(id: Int) async throws -> User { // 5-second timeout on network call try await withTimeout(seconds: 5) { try await self.fetchUser(id: id) } } private func fetchUser(id: Int) 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) } } ``` **ทางเลือกสมัยใหม่**: ตั้งแต่ iOS 16 ใช้ `URLSession` ด้วย `timeoutInterval` ที่กำหนดค่าผ่าน `URLSessionConfiguration` โดยเฉพาะสำหรับการเรียก HTTP > **การยกเลิกที่ชัดเจน** > > `group.cancelAll()` มีความสำคัญในการปลดปล่อยทรัพยากร หากไม่มีสิ่งนี้ งานที่แพ้จะดำเนินการต่อในเบื้องหลังจนกว่าจะเสร็จสิ้นโดยธรรมชาติ ทำให้สิ้นเปลือง CPU และหน่วยความจำ ## จะแบ่งปันสถานะที่เปลี่ยนแปลงได้อย่างปลอดภัยระหว่างงานหลายอย่างได้อย่างไร? **คำตอบที่คาดหวัง**: ใช้ `actor` สำหรับสถานะที่แบ่งปัน หรือ `AsyncStream` เพื่อสื่อสารระหว่างงานผ่านสตรีมของค่า ```swift // SharedStateManager.swift // Safe state sharing between concurrent tasks // Approach 1: Actor for shared state with sequential access actor DownloadManager { private var activeDownloads: [String: Task] = [:] private var cache: [String: Data] = [:] // Start download or return existing task func download(url: String) async throws -> Data { // Check cache first if let cachedData = cache[url] { return cachedData } // Check if download already in progress if let existingTask = activeDownloads[url] { return try await existingTask.value } // Create new download task let task = Task { let data = try await self.performDownload(url: url) // Update cache (thread-safe via actor) await self.completeDownload(url: url, data: data) return data } activeDownloads[url] = task return try await task.value } private func performDownload(url: String) async throws -> Data { let urlObject = URL(string: url)! let (data, _) = try await URLSession.shared.data(from: urlObject) return data } private func completeDownload(url: String, data: Data) { cache[url] = data activeDownloads.removeValue(forKey: url) } } // Approach 2: AsyncStream for inter-task communication struct EventStream { private let continuation: AsyncStream.Continuation let stream: AsyncStream init() { var continuation: AsyncStream.Continuation! stream = AsyncStream { cont in continuation = cont } self.continuation = continuation } func emit(_ event: Event) { continuation.yield(event) } func finish() { continuation.finish() } } // Example: shared progress monitoring func processItemsWithProgress(items: [Item]) async { let eventStream = EventStream() // Task 1: Process items Task { for item in items { await processItem(item) eventStream.emit(.itemProcessed(item.id)) } eventStream.finish() } // Task 2: Update UI with progress Task { @MainActor in for await event in eventStream.stream { switch event { case .itemProcessed(let id): print("Item \(id) processed") } } } } enum Event { case itemProcessed(String) } ``` **ตัวเลือกสถาปัตยกรรม**: Actor สำหรับสถานะแบบรวมศูนย์ที่มีตรรกะธุรกิจ AsyncStream สำหรับการสื่อสารแบบ event-driven ระหว่างคอมโพเนนต์ที่แยกออกจากกัน ## การยกเลิก Task คืออะไรและจัดการอย่างไร? **คำตอบที่คาดหวัง**: การยกเลิก Task ช่วยให้สามารถยกเลิกการดำเนินการอะซิงโครนัสที่กำลังดำเนินการอยู่ งานต้องตรวจสอบ `Task.isCancelled` เป็นระยะหรือใช้ `Task.checkCancellation()` ที่โยนข้อผิดพลาด ```swift // CancellableOperations.swift // Implementing proper task cancellation struct ImageProcessor { // Cancellable processing with explicit checks func processImages(_ images: [UIImage]) async throws -> [ProcessedImage] { var results: [ProcessedImage] = [] for (index, image) in images.enumerated() { // Check 1: Boolean check (continue or skip) if Task.isCancelled { print("Cancelled after \(index) images") break // Graceful stop } let processed = try await processImage(image) results.append(processed) // Check 2: Automatic throw if cancelled try Task.checkCancellation() } return results } private func processImage(_ image: UIImage) async throws -> ProcessedImage { // Simulate long processing for _ in 0..<10 { try await Task.sleep(nanoseconds: 100_000_000) // ✅ Check cancellation in long loops try Task.checkCancellation() } return ProcessedImage(from: image) } } // SwiftUI: Automatic cancellation when view disappears struct ImageGalleryView: View { @State private var images: [ProcessedImage] = [] var body: some View { ScrollView { // Display images } .task { // ✅ Task cancelled automatically when view disappears let processor = ImageProcessor() do { images = try await processor.processImages(sourceImages) } catch is CancellationError { print("Processing cancelled") } } } } // Manual cancellation of stored task class DownloadViewModel { private var downloadTask: Task? func startDownload() { downloadTask = Task { do { try await performLongDownload() } catch is CancellationError { print("Download cancelled by user") } } } func cancelDownload() { // Explicit cancellation of stored task downloadTask?.cancel() downloadTask = nil } private func performLongDownload() async throws { try Task.checkCancellation() // Download logic } } ``` **ประเด็นสำคัญ**: - `Task.isCancelled`: การตรวจสอบที่ไม่บล็อก (คืนค่า bool) - `Task.checkCancellation()`: โยน `CancellationError` หากถูกยกเลิก - SwiftUI modifier `.task { }`: การยกเลิกอัตโนมัติเมื่อ view หายไป > **การยกเลิกแบบร่วมมือ** > > Swift ใช้รูปแบบการยกเลิกแบบร่วมมือ: งานจะไม่ถูกฆ่าโดยบังคับ โค้ดต้องตรวจสอบ `Task.isCancelled` หรือ `checkCancellation()` อย่างแข็งขันเพื่อตอบสนองต่อการยกเลิก หากไม่มีการตรวจสอบเหล่านี้ งานจะดำเนินต่อไปอย่างไม่มีกำหนด ## จะใช้ MainActor อย่างถูกต้องในแอปพลิเคชัน SwiftUI ได้อย่างไร? **คำตอบที่คาดหวัง**: ใส่ annotation ViewModel ด้วย `@MainActor` เพื่อรับประกันว่าการอัปเดตสถานะ UI ทั้งหมดเกิดขึ้นบน main thread ใช้ `@MainActor` กับฟังก์ชันแต่ละรายการหากเฉพาะการดำเนินการบางอย่างเท่านั้นที่แตะ UI ```swift // MainActorPatterns.swift // Proper MainActor usage in SwiftUI architecture // Pattern 1: Entire ViewModel @MainActor @MainActor class UserViewModel: ObservableObject { @Published var user: User? @Published var isLoading = false @Published var errorMessage: String? private let repository: UserRepository init(repository: UserRepository) { self.repository = repository } // ✅ All methods implicitly @MainActor func loadUser(id: Int) async { isLoading = true // No need for await or MainActor.run errorMessage = nil do { // Network call done on background thread by runtime user = try await repository.fetchUser(id: id) } catch { errorMessage = error.localizedDescription } isLoading = false // Always on MainActor } // Synchronous method also on MainActor func clearUser() { user = nil errorMessage = nil } } // Pattern 2: Selective methods with @MainActor class DataSyncService { // ❌ Not @MainActor on class (no UI here) func syncData() async throws { // Background processing let data = try await fetchRemoteData() let processed = processData(data) // ✅ Switch to MainActor only for UI await updateUI(with: processed) } @MainActor private func updateUI(with data: ProcessedData) { // Update observable property NotificationCenter.default.post( name: .dataDidSync, object: data ) } // Background work (not @MainActor) private func fetchRemoteData() async throws -> Data { // Network call Data() } private func processData(_ data: Data) -> ProcessedData { // CPU-intensive processing in background ProcessedData() } } // Pattern 3: Closure annotation class ImageLoader { func loadImage(url: URL, completion: @MainActor @escaping (UIImage?) -> Void) async { let image = try? await downloadImage(from: url) // ✅ Completion guaranteed on MainActor await completion(image) } private func downloadImage(from url: URL) async throws -> UIImage { let (data, _) = try await URLSession.shared.data(from: url) return UIImage(data: data) ?? UIImage() } } ``` **ข้อผิดพลาดทั่วไป**: การทำเครื่องหมายทั้งคลาสเป็น `@MainActor` เมื่อมีเพียงเมธอดบางอย่างเท่านั้นที่แตะ UI สิ่งนี้บังคับให้โค้ดทั้งหมดทำงานบน main thread รวมถึงการดำเนินการที่หนักหน่วงซึ่งควรอยู่ในเบื้องหลัง ## จะจัดการ data races ด้วย Sendable ได้อย่างไร? **คำตอบที่คาดหวัง**: โปรโตคอล `Sendable` รับประกันว่าประเภทสามารถแชร์ระหว่างงานได้โดยไม่มีความเสี่ยงต่อ data race ประเภทค่า (struct, enum) เป็น `Sendable` โดยอัตโนมัติ คลาสต้องเป็น `final` ที่มีคุณสมบัติที่ไม่เปลี่ยนแปลงหรือได้รับการป้องกัน ```swift // SendableCompliance.swift // Making types safe for concurrent access // ✅ Struct: automatically Sendable (value type) struct UserData: Sendable { let id: Int let name: String let email: String } // ✅ Enum: automatically Sendable enum LoadingState: Sendable { case idle case loading case loaded(UserData) case failed(Error) // ⚠️ Error must also be Sendable } // ❌ Class with mutable state: not Sendable by default class UnsafeCounter { var count = 0 // Mutable, unprotected func increment() { count += 1 // Data race possible } } // ✅ Immutable class: explicit Sendable final class SafeConfig: @unchecked Sendable { let apiKey: String let timeout: TimeInterval init(apiKey: String, timeout: TimeInterval) { self.apiKey = apiKey self.timeout = timeout } } // ✅ Class with actor-protected state actor SafeCounter: Sendable { private var count = 0 // Protected by actor isolation func increment() { count += 1 // Thread-safe automatically } func getValue() -> Int { return count } } // ✅ Class with manually protected state final class ThreadSafeCache: @unchecked Sendable { private let lock = NSLock() private var storage: [String: Data] = [:] func store(_ data: Data, for key: String) { lock.lock() defer { lock.unlock() } storage[key] = data } func retrieve(for key: String) -> Data? { lock.lock() defer { lock.unlock() } return storage[key] } } // Usage: compiler checks Sendable func processInBackground(data: UserData) { // ✅ UserData is Sendable Task.detached { // No warning: UserData is Sendable value type print("Processing user: \(data.name)") } } func processUnsafe(counter: UnsafeCounter) { Task.detached { // ⚠️ Warning: UnsafeCounter is not Sendable // counter.increment() } } ``` **กฎ Sendable**: - Struct/Enum ที่มีคุณสมบัติ Sendable: เป็น Sendable โดยอัตโนมัติ - คลาส: ต้องเป็น `final` + ไม่เปลี่ยนแปลง หรือใช้ `@unchecked Sendable` พร้อมการป้องกันแบบแมนนวล (lock, actor) - Closure: เป็น Sendable โดยอัตโนมัติหากจับเฉพาะประเภท Sendable > **@unchecked Sendable** > > `@unchecked Sendable` ปิดการใช้งานการตรวจสอบของคอมไพเลอร์ ใช้เฉพาะเมื่อมีการรับประกัน thread-safety ด้วยตนเอง (lock, serial queue) เป็นความรับผิดชอบของนักพัฒนาในการหลีกเลี่ยง data races ## บทสรุป การเชี่ยวชาญ Swift Structured Concurrency กลายเป็นสิ่งจำเป็นสำหรับการสัมภาษณ์ iOS ในปี 2026 ผู้สรรหาทดสอบสามระดับ: ความเข้าใจในแนวคิด (async/await กับ callback), ความเชี่ยวชาญในรูปแบบ (TaskGroup, actor isolation) และการดีบัก (การยกเลิก, Sendable) **รายการตรวจสอบการเตรียมตัว**: - ✅ อธิบาย async/await กับ DispatchQueue ด้วยตัวอย่างที่เป็นรูปธรรม - ✅ แสดงการใช้ TaskGroup สำหรับการดำเนินการแบบขนาน - ✅ ใช้งาน actor ที่ปลอดภัยต่อ thread เพื่อปกป้องสถานะที่เปลี่ยนแปลงได้ - ✅ จัดการข้อผิดพลาดในบริบทที่เกิดขึ้นพร้อมกัน (Result, throwing) - ✅ แยกแยะ Task, Task.detached และ async let พร้อมกรณีการใช้งาน - ✅ ใช้งาน timeout บนการดำเนินการอะซิงโครนัส - ✅ ใช้ MainActor อย่างถูกต้องในสถาปัตยกรรม SwiftUI - ✅ เข้าใจ Sendable และหลีกเลี่ยง data races ผู้สมัครที่ดีที่สุดผสมผสานทฤษฎีและการปฏิบัติ: อธิบาย "ทำไม" (หลีกเลี่ยง data races, ปรับปรุงการอ่าน) และ "อย่างไร" (โค้ดที่ทำงานได้พร้อมการจัดการข้อผิดพลาด) ฝึกฝนกับโครงการจริงเพื่อรวบรวมรูปแบบเหล่านี้ --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/ios/swift-structured-concurrency-interview-questions-async-await-taskgroup-actors