.task { } — คืออะไร, ตัวปรับแก้ async และการโหลดข้อมูลใน View

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-26 เวลาอ่าน: 9 นาที

.task { } คือตัวปรับแก้ใน SwiftUI ที่เปิดตัวใน iOS 15 ซึ่งเริ่มดำเนินการแบบอะซิงโครนัสเมื่อ View ปรากฏขึ้นและยกเลิกโดยอัตโนมัติเมื่อ View หายไป แตกต่างจาก .onAppear ที่ทำงานโค้ดแบบซิงโครนัสโดยไม่มีความสามารถในการยกเลิก .task ทำงานกับบริบท async/await และคำนึงถึงวงจรชีวิตของ View: เมื่อ View หายไป SwiftUI จะเรียก cancel() บน Task ที่สร้างขึ้น ซึ่งป้องกันการรั่วไหลของหน่วยความจำและการดำเนินการหลังจากที่ View ไม่จำเป็นต้องอัปเดตอีกต่อไป ตามข้อมูลจาก Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024) .task เป็นวิธีที่ต้องการในการโหลดข้อมูลใน SwiftUI เนื่องจากทำงานอย่างปลอดภัยกับ Structured Concurrency และจัดการอายุการทำงานของการดำเนินการแบบอะซิงโครนัสโดยอัตโนมัติ

ประเด็นสำคัญ

  • .task { } — ตัวปรับแก้ SwiftUI สำหรับการโหลดข้อมูลแบบอะซิงโครนัสเมื่อ View ปรากฏขึ้น ใช้งานได้ตั้งแต่ iOS 15
  • การยกเลิกอัตโนมัติ — เมื่อ View หายไป SwiftUI จะยกเลิก Task ป้องกันการรั่วไหลของหน่วยความจำ
  • บริบท async/await — ภายใน .task การเรียก async พร้อมใช้งานโดยไม่ต้องใช้ DispatchQueue หรือ Combine
  • .task(id:) — รูปแบบที่มีตัวระบุซึ่งเริ่มงานใหม่เมื่อค่าที่ระบุเปลี่ยนแปลง
  • Structured Concurrency — .task รองรับ Structured Concurrency และ TaskGroup สำหรับการดำเนินการแบบขนาน

.task { } ใน SwiftUI คืออะไร

.task { } คือตัวปรับแก้ View ที่สร้าง Task ในบริบท async เมื่อ View ปรากฏขึ้นบนหน้าจอ SwiftUI จะดำเนินการ closure ที่ให้ไว้ในเธรดพื้นหลัง โดยปล่อยให้เธรดหลักว่างสำหรับการดำเนินการ UI เมื่อ View หายไป SwiftUI จะยกเลิก Task โดยอัตโนมัติผ่านกลไก Structured Concurrency — ซึ่งรับประกันว่าการดำเนินการแบบอะซิงโครนัสจะไม่ทำงานต่อหลังจากที่ไม่ต้องการผลลัพธ์อีกต่อไป

ตามข้อมูลจาก Apple — Swift Programming Language (2025) .task ใช้แนวคิดของ Structured Concurrency ที่นำมาใช้ใน Swift 5.5 แต่ละ .task สร้างงานย่อยภายใต้งานของ View หลัก หากงานหลักถูกยกเลิก (View หายไป) งานย่อยทั้งหมดก็จะถูกยกเลิกโดยอัตโนมัติเช่นกัน ซึ่งช่วยลดความซับซ้อนในการจัดการวงจรชีวิตของการดำเนินการแบบอะซิงโครนัสเมื่อเทียบกับการเก็บข้อมูลอ้างอิงไปยัง DispatchWorkItem หรือ AnyCancellable ด้วยตนเอง

แตกต่างจากวิธีการดั้งเดิมด้วย @State + การเรียกด้วยตนเองใน .onAppear .task ไม่จำเป็นต้องเก็บข้อมูลอ้างอิงไปยัง Task สำหรับการยกเลิกในภายหลัง SwiftUI ทำสิ่งนี้โดยอัตโนมัติ ลดโค้ดเทมเพลตและขจัดความเสี่ยงในการลืมยกเลิกงาน

swift
struct ArticlesView: View {
    @State var articles: [Article] = []
    @State var error: Error?
    
    var body: some View {
        List(articles) { article in
            Text(article.title)
        }
        .task {
            do {
                articles = await APIClient().fetchArticles()
            } catch {
                self.error = error
            }
        }
    }
}

.task vs .onAppear: ความแตกต่างหลัก

นักพัฒนาหลายคนคุ้นเคยกับการโหลดข้อมูลใน .onAppear แต่ด้วยการมาของ async/await และ .task วิธีการนี้จึงล้าสมัย .onAppear ดำเนินการโค้ดแบบซิงโครนัส — สำหรับการดำเนินการแบบอะซิงโครนัสภายใน .onAppear จำเป็นต้องห่อการเรียกใน Task { } และเก็บข้อมูลอ้างอิงด้วยตนเองสำหรับการยกเลิกที่เป็นไปได้ .task ทำสิ่งนี้โดยอัตโนมัติ

คุณลักษณะ.task { }.onAppear
บริบท asyncasync/await ในตัวต้องการ wrapper Task { }
การยกเลิกอัตโนมัติใช่ เมื่อ View หายไปไม่ ต้องดำเนินการด้วยตนเอง
Structured Concurrencyรองรับไม่รองรับ
การทำงานซ้ำเฉพาะเมื่อ id เปลี่ยนแปลงทุกครั้งที่ View ปรากฏ
คำแนะนำของ Appleวิธีที่ต้องการสำหรับการดำเนินการแบบซิงโครนัส

.onAppear ยังคงมีประโยชน์สำหรับการดำเนินการแบบซิงโครนัส — ตัวอย่างเช่น การบันทึกหรือการตั้งค่า UI เริ่มต้น แต่สำหรับการโหลดข้อมูลแบบอะซิงโครนัส คำขอเครือข่าย การทำงานกับฐานข้อมูลหรือระบบไฟล์ ให้ใช้ .task ซึ่งปลอดภัยกว่าและสะอาดกว่าจากมุมมองทางสถาปัตยกรรม

swift
// ❌ วิธีเดิม: Task ใน .onAppear โดยไม่มีการยกเลิก
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
    .onAppear {
        loadTask = Task { await loadData() }
    }
    .onDisappear { loadTask?.cancel() }

// ✅ วิธีสมัยใหม่: .task จัดการการยกเลิก
func body() { var body: some View { Text("") }
    .task { await loadData() }

.task(id:) — เริ่มใหม่เมื่อข้อมูลเปลี่ยนแปลง

ตัวปรับแก้ .task(id:) ยอมรับพารามิเตอร์เพิ่มเติม — ตัวระบุ เมื่อค่าของตัวระบุเปลี่ยนแปลง SwiftUI จะยกเลิกงานปัจจุบันและเริ่มงานใหม่ด้วยตัวระบุใหม่ ซึ่งเหมาะสำหรับหน้าจอที่ข้อมูลขึ้นอยู่กับพารามิเตอร์ที่เลือก — ตัวอย่างเช่น รายการบทความตามหมวดหมู่หรือรายละเอียดสินค้าตาม ID

swift
struct CategoryView: View {
    let categoryId: Int
    @State var items: [Item] = []
    
    var body: some View {
        List(items) { item in
            Text(item.name)
        }
        .task(id: categoryId) {
            await loadItems(for: categoryId)
        }
    }
    
    func loadItems(for id: Int) async {
        do {
            items = await APIClient().fetchItems(categoryId: id)
        } catch {
            // จัดการข้อผิดพลาด
        }
    }
}

เมื่อ categoryId เปลี่ยนแปลง SwiftUI จะยกเลิกคำขอก่อนหน้าและเริ่มคำขอใหม่ ซึ่งสำคัญอย่างยิ่งเมื่อสลับหมวดหมู่อย่างรวดเร็ว — คำขอเก่าจะไม่แข่งขันกับคำขอใหม่ในการอัปเดตสถานะ หากไม่มี .task(id:) คุณจะต้องติดตามการเปลี่ยนแปลงด้วยตนเองผ่าน .onChange และจัดการ Task ด้วยตนเอง

การยกเลิกงานและการตรวจสอบ isCancelled

แม้ว่า .task จะยกเลิกงานโดยอัตโนมัติเมื่อ View หายไป แต่การดำเนินการแบบอะซิงโครนัสจะต้องตรวจสอบการยกเลิกแบบร่วมมือกัน Swift ใช้โมเดลการยกเลิกแบบร่วมมือ — Task.cancel() ไม่ได้หยุดการทำงานโดยบังคับ แต่เพียงตั้งค่าแฟล็ก isCancelled เท่านั้น โค้ดภายในงานควรตรวจสอบแฟล็กนี้เป็นระยะ

swift
struct LoadingView: View {
    @State var progress: Double = 0
    
    var body: some View {
        ProgressView(value: progress)
            .task {
                for i in 0..<100 {
                    // ตรวจสอบการยกเลิก
                    try Task.checkCancellation()
                    
                    await Task.sleep(nanoseconds: 50_000_000)
                    progress = Double(i + 1) / 100.0
                }
            }
    }
}

Task.checkCancellation() จะโยน CancellationError หากงานถูกยกเลิก นี่เป็นวิธีตรวจสอบที่ง่ายที่สุด — ทำงานในบริบท async ใดๆ ทางเลือกคือตรวจสอบ Task.isCancelled ด้วยตนเองก่อนดำเนินการที่มีค่าใช้จ่ายสูง สำหรับ URLSession คำขอเครือข่ายจะถูกยกเลิกโดยอัตโนมัติเมื่องานถูกยกเลิก เนื่องจาก URLSession รองรับ Structured Concurrency ตั้งแต่เริ่มต้น

ตัวอย่างการใช้งาน .task

.task เหมาะสำหรับหลายสถานการณ์: ตั้งแต่การโหลด JSON อย่างง่ายไปจนถึงการดำเนินการแบบขนานที่ซับซ้อนด้วย TaskGroup มาดูสามกรณีการใช้งานทั่วไป

การโหลดพร้อมจัดการข้อผิดพลาด

swift
struct ProfileView: View {
    @State var profile: Profile?
    @State var isLoading = true
    
    var body: some View {
        Group {
            if isLoading {
                ProgressView()
            } else if let profile {
                Text(profile.name)
            } else {
                Text("โหลดล้มเหลว")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // โปรไฟล์ยังคงเป็น nil
            }
        }
    }
}

การโหลดแบบขนานด้วย TaskGroup

swift
struct DashboardView: View {
    @State var stats: DashboardStats?
    
    var body: some View {
        Text("แดชบอร์ด")
            .task {
                stats = await Task {
                    await withThrowingTaskGroup { group in
                        group.addTask { await API().fetchUsers() }
                        group.addTask { await API().fetchOrders() }
                        group.addTask { await API().fetchRevenue() }
                        
                        return DashboardStats(
                            users: try await group.next(),
                            orders: try await group.next(),
                            revenue: try await group.next()
                        )
                    }
                }.value
            }
    }
}

ข้อผิดพลาดทั่วไปกับ .task

ข้อผิดพลาดที่พบบ่อยที่สุดคือการเปลี่ยนแปลงคุณสมบัติ UI ภายใน .task โดยไม่สลับไปยังเธรดหลัก แม้ว่า SwiftUI จะส่งคืนการอัปเดตไปยังเธรดหลักโดยอัตโนมัติเมื่อแก้ไข @State ในบริบท async แต่การจัดการองค์ประกอบ UIKit โดยตรงภายใน .task อาจทำให้เกิดการหยุดทำงาน

  • ลืม try/catch — .task ไม่จัดการข้อผิดพลาดโดยอัตโนมัติ ฟังก์ชันที่โยนข้อผิดพลาดทั้งหมดต้องถูกห่อใน do/catch มิฉะนั้นแอปจะหยุดทำงาน
  • สภาพแข่งขัน — หาก .task(id:) หลายตัวเริ่มทำงานด้วย id ที่แตกต่างกันและอัปเดตสถานะเดียวกัน อาจเกิดสภาพแข่งขันได้ ใช้คุณสมบัติแยกกันสำหรับแหล่งข้อมูลที่แตกต่างกัน
  • การดำเนินการแบบซิงโครนัสที่ยาวนาน — .task ไม่ได้ทำให้โค้ดแบบซิงโครนัสกลายเป็นอะซิงโครนัส หากมีงานซิงโครนัสที่หนักภายใน .task ให้ห่อใน Task.detached หรือย้ายไปยังเมธอด async แยกต่างหาก
  • ละเว้น CancellationError — เมื่อตรวจสอบ Task.checkCancellation() ควรส่ง CancellationError ขึ้นไปข้างบน ไม่ใช่ระงับไว้ การระงับการยกเลิกอาจนำไปสู่การรั่วไหลของหน่วยความจำ
swift
// ❌ ข้อผิดพลาด: ไม่มีการจัดการข้อผิดพลาด
.task {
    let data = await fetchData() // หยุดทำงานเมื่อมีข้อผิดพลาด!
    items = data
}

// ✅ ถูกต้อง: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

คำถามที่พบบ่อย

ความแตกต่างระหว่าง .task และการใช้ Task { } ใน .onAppear คืออะไร?

.task ยกเลิกงานโดยอัตโนมัติเมื่อ View หายไปและรองรับ Structured Concurrency Task { } ใน .onAppear ต้องเก็บข้อมูลอ้างอิงไปยังงานด้วยตนเองและเรียก cancel() ใน .onDisappear .task ยังอ่านง่ายกว่า — มันบ่งชี้อย่างชัดเจนว่าการโหลดข้อมูลเป็นส่วนหนึ่งของวงจรชีวิตของ View

สามารถใช้ .task เพื่อสมัครรับ publisher ได้หรือไม่?

ใช่ หากต้องการสมัครรับ AsyncSequence หรือ AsyncStream ให้ใช้ for await value in publisher.values ภายใน .task ซึ่งทำงานได้ทั้งกับ async/await และ Combine ผ่านส่วนขยาย Publisher.values เมื่อ View หายไป การวนซ้ำจะสิ้นสุดโดยอัตโนมัติและการสมัครรับจะถูกยกเลิก

.task ทำงานกับ TabView อย่างไร — งานจะเริ่มใหม่เมื่อเปลี่ยนแท็บหรือไม่?

ใช่ หาก View ภายใน TabView ถูกสร้างใหม่ทุกครั้งที่เปลี่ยนแท็บ ตั้งแต่ iOS 18 เป็นต้นไป TabView สามารถเก็บ Views ไว้ในหน่วยความจำ — ในกรณีนี้ .task จะไม่เริ่มใหม่ ใช้ .task(id:) พร้อมตัวระบุแท็บหากคุณต้องการโหลดข้อมูลใหม่ทุกครั้งที่เปลี่ยนแท็บ

จะเกิดอะไรขึ้นหาก View ที่มี .task หายไปก่อนที่คำขอจะเสร็จสิ้น?

SwiftUI จะยกเลิกงานเมื่อ View หายไป หากคำขอ URLSession อยู่ภายในงาน มันก็จะถูกยกเลิกเช่นกัน หากงานไม่รองรับการยกเลิกแบบร่วมมือ (เช่น ไม่ตรวจสอบ isCancelled) มันจะทำงานต่อไป แต่ผลลัพธ์จะไม่ถูกนำไปใช้กับสถานะเนื่องจาก View ไม่มีอยู่อีกต่อไป

สามารถใช้ตัวปรับแก้ .task หลายตัวบน View เดียวได้หรือไม่?

ใช่ คุณสามารถเพิ่มตัวปรับแก้ .task หลายตัวบน View เดียวได้ แต่ละตัวสร้างงานอิสระ ซึ่งมีประโยชน์สำหรับการแยกแหล่งข้อมูลที่แตกต่างกัน: .task หนึ่งสำหรับโหลดโปรไฟล์ อีกอันสำหรับสมัครรับ WebSocket อันที่สามสำหรับติดตามตำแหน่งทางภูมิศาสตร์

สรุป

  • .task { } — ตัวปรับแก้ SwiftUI สำหรับการดำเนินการแบบอะซิงโครนัสพร้อมการยกเลิกอัตโนมัติเมื่อ View หายไป
  • รองรับ async/await — ภายใน .task มีบริบท async เต็มรูปแบบโดยไม่ต้องใช้ wrapper Task
  • .task(id:) — เริ่มงานใหม่เมื่อตัวระบุเปลี่ยนแปลง แทนที่ .onChange ด้วยตนเอง
  • การยกเลิกแบบร่วมมือ — ใช้ Task.checkCancellation() เพื่อตรวจสอบการยกเลิกภายในงาน
  • Structured Concurrency — .task รองรับ TaskGroup และการดำเนินการแบบขนานพร้อมการยกเลิกงานย่อย
  • แทนที่ .onAppear — สำหรับการโหลดข้อมูลแบบอะซิงโครนัส ให้ใช้ .task แทนรูปแบบ .onAppear + Task + .onDisappear
  • การจัดการข้อผิดพลาด — การเรียก async ทั้งหมดภายใน .task ต้องถูกห่อใน do/catch เพื่อป้องกันการหยุดทำงาน

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม