@MainActor — คืออะไร การใช้งาน และคุณสมบัติในโค้ดแบบอะซิงโครนัสของ Swift

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

@MainActor เป็นแอ็กเตอร์ระดับโลกในภาษา Swift ที่รับประกันการทำงานของโค้ดบนเธรดหลัก ตามข้อมูลจาก Apple Developer, 2024 @MainActor จะทำให้การสลับไปยังเธรดหลักเป็นไปโดยอัตโนมัติเมื่อทำงานกับ UI ช่วยให้นักพัฒนาไม่ต้องเรียก DispatchQueue.main.async ด้วยตนเอง คำอธิบายประกอบนี้ปรากฏใน Swift 5.5 พร้อมกับระบบ async/await

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

  • @MainActor — แอ็กเตอร์ระดับโลกของ Swift สำหรับรับประกันการทำงานบนเธรดหลัก
  • ระบบ async/await — พื้นฐานที่ @MainActor ถูกสร้างขึ้นมา
  • คำอธิบายประกอบคลาส จะวางเมธอดทั้งหมดบนเธรดหลักโดยอัตโนมัติ
  • แตกต่างจาก DispatchQueue.main @MainActor ตรวจสอบเธรดในระดับคอมไพเลอร์
  • การอัปเดต UI — ขอบเขตการใช้งานหลักของ @MainActor ในการพัฒนา iOS

@MainActor คืออะไร?

@MainActor เป็นแอ็กเตอร์ระดับโลกใน Swift ที่รวมคุณสมบัติของแอ็กเตอร์เข้ากับการรับประกันการทำงานบนเธรดหลักของแอปพลิเคชัน มันเป็นส่วนหนึ่งของระบบการทำงานพร้อมกันของ Swift ที่เปิดตัวใน Swift 5.5 พร้อมกับ async/await และการทำงานพร้อมกันแบบมีโครงสร้าง คำอธิบายประกอบช่วยให้นักพัฒนาไม่ต้องกังวลเกี่ยวกับการสลับเธรดด้วยตนเองและลดจำนวนข้อผิดพลาดของ UI

ความหมายและตำแหน่งใน Swift Concurrency

แอ็กเตอร์ใน Swift เป็นชนิดอ้างอิงที่แยกสถานะของตัวเองและรับประกันว่ามีเพียงเธรดเดียวเท่านั้นที่สามารถแก้ไขได้ @MainActor เป็น แอ็กเตอร์ระดับโลก พิเศษซึ่งมีตัวดำเนินการคือเธรดหลัก โค้ดใดๆ ที่ถูกทำเครื่องหมายด้วย @MainActor จะทำงานบนเธรดหลัก — แม้ว่าจะถูกเรียกจากงานเบื้องหลังก็ตาม

ก่อน @MainActor นักพัฒนาเปลี่ยนไปยังเธรดหลักด้วยตนเองผ่าน DispatchQueue.main.async นี่เป็นแหล่งที่มาของข้อผิดพลาดบ่อยครั้ง: นักพัฒนาลืมสลับ ทำให้เกิดการค้างเนื่องจากการอัปเดต UI ที่ไม่ได้อยู่บนเธรดหลัก @MainActor แก้ปัญหานี้ได้ในระดับระบบชนิด

เหตุผลในการสร้าง

ที่มาของข้อบกพร่องส่วนใหญ่ในแอปพลิเคชัน iOS คือ ความไม่ปลอดภัยของ UI — การอัปเดตอินเทอร์เฟซจากเธรดเบื้องหลัง Apple ได้รวม @MainActor เข้าไปใน Swift Concurrency เพื่อทำให้การสลับไปยังเธรดหลักเป็นแบบอัตโนมัติและตรวจสอบได้โดยคอมไพเลอร์ ซึ่งกำจัดข้อผิดพลาดรันไทม์ทั้งคลาส

@MainActor ทำงานอย่างไร?

หลักการทำงานของ @MainActor ขึ้นอยู่กับระบบการทำงานของ Swift Concurrency เมื่อเธรดเรียกฟังก์ชันที่ทำเครื่องหมายด้วย @MainActor ตัวจัดตารางเวลาจะระงับมันบนตัวดำเนินการปัจจุบันและดำเนินการต่อบนเธรดหลัก คอมไพเลอร์จะติดตามขอบเขตการเรียกและรับประกันความปลอดภัย

ตัวดำเนินการเธรดหลัก

การทำงานของ @MainActor จัดการโดย MainActor.shared — ตัวดำเนินการที่เชื่อมโยงกับเธรดหลักของแอปพลิเคชัน เมื่อฟังก์ชันแบบอะซิงโครนัสถูกทำเครื่องหมายด้วย @MainActor มันจะดำเนินการต่อบนตัวดำเนินการนี้เสมอ ไม่ว่าเธรดใดที่งานเดิมถูกเริ่มต้น

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // อย่างปลอดภัย MainActor รับประกันเธรดหลัก
    }
}

การสืบทอดบริบทของแอ็กเตอร์

หากฟังก์ชันถูกทำเครื่องหมายด้วย @MainActor และเรียกฟังก์ชันแบบอะซิงโครนัสอื่น มันจะสืบทอด บริบทของแอ็กเตอร์ โดยค่าเริ่มต้น ซึ่งหมายความว่าการเรียกที่ซ้อนกันทั้งหมดก็ทำงานบนเธรดหลักเช่นกัน เว้นแต่จะระบุเป็นอย่างอื่น คอมไพเลอร์ติดตามสิ่งนี้และแสดงข้อผิดพลาดเมื่อพยายามส่งคลosure ที่ไม่สอดคล้องกัน

@MainActor vs DispatchQueue.main

การเปรียบเทียบ @MainActor และ DispatchQueue.main ช่วยให้เข้าใจว่าทำไมกลไกใหม่จึงถือว่าปลอดภัยและสะดวกกว่า แม้ว่าทั้งสองจะแก้ปัญหาเดียวกัน — การทำงานของโค้ดบนเธรดหลัก

ความปลอดภัยในระดับชนิด

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

ประสิทธิภาพและค่าใช้จ่าย

DispatchQueue.main.async เพิ่มบล็อกลงในคิวที่อาจทำงานด้วยความล่าช้า @MainActor กับ async/await ทำ การสลับตัวดำเนินการโดยตรง โดยไม่สร้างคลosure ที่ไม่จำเป็น ซึ่งช่วยลดค่าใช้จ่ายและทำให้เวลาทำงานคาดเดาได้มากขึ้น

swift
// แนวทางเก่า
DispatchQueue.main.async {
    self.updateUI()
}

// แนวทางใหม่กับ @MainActor
@MainActor
func updateUI() {
    // ทำงานบนเธรดหลัก
    self.label.text = "อัปเดตแล้ว"
}
เกณฑ์@MainActorDispatchQueue.main
การตรวจสอบคอมไพเลอร์รันไทม์
ไวยากรณ์คำอธิบายประกอบ (เชิงประกาศ)การเรียก (เชิงคำสั่ง)
ค่าใช้จ่ายต่ำ (การสลับตัวดำเนินการ)ปานกลาง (คลosure + คิว)
ความสามารถในการทดสอบสูง (สามารถเปลี่ยน MainActor.shared ได้)ต่ำ (ยากที่จะจำลอง)

การใช้งาน @MainActor ในโปรเจกต์ iOS

ในโปรเจกต์ iOS จริง @MainActor ถูกใช้ในชั้น ViewModel มุมมอง SwiftUI และตัวควบคุม UIKit คำอธิบายประกอบสามารถใช้ได้ทั้งกับเมธอดเดี่ยวและทั้งชนิด

คำอธิบายประกอบคลาสหรือโครงสร้าง

การทำเครื่องหมายคลาสด้วย @MainActor คุณรับประกันว่า เมธอดและคุณสมบัติทั้งหมด สามารถเข้าถึงได้เฉพาะบนเธรดหลักเท่านั้น สิ่งนี้สะดวกเป็นพิเศษสำหรับมุมมอง SwiftUI และคลาส ObservableObject: คุณเพียงเพิ่ม @MainActor หน้า class และคุณสมบัติ @Published ทั้งหมดจะอัปเดตอย่างปลอดภัย

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

การห่อหุ้มโค้ดดั้งเดิม

เมื่อทำงานกับโค้ด UIKit ดั้งเดิมที่การสลับเธรดเป็นแบบแมนนวล คุณสามารถใช้ MainActor.run สำหรับการสลับอย่างชัดเจน สิ่งนี้สะดวกสำหรับการเปลี่ยนผ่านไปยัง Swift Concurrency แบบค่อยเป็นค่อยไปโดยไม่ต้องเขียนฐานโค้ดทั้งหมดใหม่

swift
await MainActor.run {
    self.tableView.reloadData()
}

ข้อจำกัดของ @MainActor

แม้จะมีข้อดีทั้งหมด @MainActor มีข้อจำกัดหลายประการที่สำคัญต้องพิจารณาเมื่อออกแบบสถาปัตยกรรมแอปพลิเคชัน การทำความเข้าใจขอบเขตของการนำไปใช้ช่วยหลีกเลี่ยงการใช้งานที่ไม่ถูกต้อง

ประสิทธิภาพเมื่อใช้งานหนัก

หากสายการเรียกทั้งหมดถูกทำเครื่องหมายด้วย @MainActor งานหนักใดๆ จะทำงานบนเธรดหลัก ทำให้ UI ค้าง ขอแนะนำให้ทำเครื่องหมายเฉพาะชั้น UI ด้วย @MainActor โดยปล่อยให้ลอจิกทางธุรกิจและคำขอเครือข่ายอยู่ในแอ็กเตอร์เบื้องหลังหรือตัวดำเนินการระดับโลก

ความไม่เข้ากันกับ API บางตัว

API แบบ callback ดั้งเดิม (เช่น URLSession ที่ไม่มี async/await) ไม่รองรับบริบทของแอ็กเตอร์ การรวมต้องใช้ตัวห่อด้วย CheckedContinuation นอกจากนี้ @MainActor ไม่เข้ากันกับ performSelector, target-action และแพทเทิร์นที่ไม่ใช่แบบอะซิงโครนัสอื่นๆ ของ UIKit

การดีบักแบบหลายเธรด

เมื่อดีบักแอปพลิเคชันด้วย @MainActor การจำลอง สภาวะการแข่งขัน ทำได้ยากกว่าเพราะคอมไพเลอร์ป้องกันหลายอย่างในเวลาสร้างแทนที่จะเป็นรันไทม์ อย่างไรก็ตาม สิ่งนี้อาจสร้างความรู้สึกปลอดภัยที่ผิดพลาด: การทำงานที่ไม่ถูกต้องกับออบเจ็กต์ที่เปลี่ยนแปลงได้ซึ่งใช้ร่วมกัน (เช่น NSCache หรือตัวแปรระดับโลกที่ใช้ร่วมกัน) ยังคงเป็นไปได้หากไม่ได้ทำเครื่องหมายด้วย @MainActor และใช้โดยไม่มีการซิงโครไนซ์อย่างชัดเจน

การทดสอบ @MainActor

@MainActor ช่วยลดความซับซ้อนของการทดสอบลอจิก UI อย่างมากเนื่องจากไม่ต้องสลับเธรดด้วยตนเองในการทดสอบ อย่างไรก็ตาม มีลักษณะเฉพาะที่ต้องพิจารณาเมื่อเขียนการทดสอบหน่วยและการทดสอบ UI

การทดสอบหน่วยกับ MainActor

ใน XCTest สภาพแวดล้อมการทดสอบจะตั้งค่า ตัวดำเนินการเธรดหลัก โดยอัตโนมัติ เมื่อเมธอดทดสอบทำงานบนเธรดหลัก การเรียกฟังก์ชัน @MainActor ไม่ต้องการการตั้งค่าเพิ่มเติม — ฟังก์ชันเหล่านี้ทำงานในบริบทเดียวกัน สำหรับการทดสอบสถานการณ์เบื้องหลัง ให้ใช้ MainActor.run ภายใน Task ที่มีลำดับความสำคัญและตัวดำเนินการอย่างชัดเจน โดยตรวจสอบแยกต่างหากว่าโค้ดทำงานถูกต้องเมื่อเรียกจากเบื้องหลัง

วิธีการทั่วไปคือการทดสอบ ViewModel กับ @MainActor ซึ่งตรวจสอบว่าคุณสมบัติ @Published อัปเดตอย่างถูกต้องหลังจากการดำเนินการแบบอะซิงโครนัส เนื่องจากการสืบทอดบริบทของแอ็กเตอร์ การเรียก await ภายในทดสอบรับประกันการทำงานบนเธรดหลักโดยไม่ต้องมีการรับประกัน DispatchQueue เพิ่มเติมหรือการสลับบริบทด้วยตนเอง ซึ่งทำให้การเขียนทดสอบง่ายขึ้น

การตรวจสอบการแยกตัวระหว่างการปรับโครงสร้าง

เมื่อปรับโครงสร้างโค้ดที่มีอยู่เป็น Swift Concurrency ให้ตรวจสอบ การแยกตัวของ @MainActor ผ่านคอมไพเลอร์: การเรียกใดๆ ไปยังเมธอดซิงโครนัสที่ไม่มี @MainActor จากบริบท @MainActor จะถูกทำเครื่องหมายเป็นข้อผิดพลาด คุณสมบัตินี้ใช้สำหรับการย้ายโปรเจกต์ไปยัง async/await แบบค่อยเป็นค่อยไป: คุณทำเครื่องหมายชั้น ViewModel เป็น @MainActor และคอมไพเลอร์จะเน้นการเรียกที่ไม่ปลอดภัยทั้งหมดที่ต้องย้ายไปยังแอ็กเตอร์เบื้องหลัง

การจำลองและบริบทของแอ็กเตอร์

เมื่อสร้างการจำลองสำหรับการพึ่งพา @MainActor ให้ใช้ โปรโตคอลที่มีเมธอด async ที่ประกาศฟังก์ชันแบบอะซิงโครนัสพร้อมชนิดส่งคืน สิ่งนี้ช่วยให้แทนที่บริการเครือข่าย ฐานข้อมูล และการพึ่งพาภายนอกอื่นๆ โดยไม่ทำลายการแยกตัวของแอ็กเตอร์ คอมไพเลอร์ตรวจสอบว่าการจำลองใช้ข้อกำหนดการแยกตัวทั้งหมด ป้องกันการเข้าถึงโค้ด @MainActor โดยไม่ได้ตั้งใจจากเธรดทดสอบเบื้องหลัง

การรอการดำเนินการแบบอะซิงโครนัส

เมื่อทดสอบโค้ด @MainActor แบบซิงโครนัส ให้ใช้ XCTestExpectation เพื่อรอให้การดำเนินการแบบอะซิงโครนัสเสร็จสมบูรณ์ ตั้งค่าความคาดหวังในการทดสอบและเรียก fulfillment ภายในคลosure ที่ทำงานบนเธรดหลัก หากการทดสอบค้างอย่างไม่มีกำหนด — แสดงว่าการเรียกบนเธรดหลักอาจไม่เกิดขึ้น และคุณต้องตรวจสอบการแยกตัวของแอ็กเตอร์ ในการดีบักบริบทการทำงาน การเพิ่มการตรวจสอบ Thread.isMainThread ภายในโค้ดทดสอบจะมีประโยชน์

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

จำเป็นต้องทำเครื่องหมายทั้งคลาสด้วย @MainActor หรือไม่?

ไม่ เพียงพอ ที่จะทำเครื่องหมายเฉพาะเมธอดที่อัปเดต UI อย่างไรก็ตาม หากคลาสมีเมธอดแบบนี้หลายวิธี การเพิ่ม @MainActor ทั้งคลาสจะง่ายกว่า สิ่งนี้รับประกันว่าสมาชิกทั้งหมดทำงานบนเธรดหลักและทำให้การบำรุงรักษาโค้ดง่ายขึ้น

@MainActor แตกต่างจาก @globalActor อย่างไร?

@MainActor เป็นอินสแตนซ์เฉพาะของแอ็กเตอร์ระดับโลกที่เชื่อมโยงกับเธรดหลัก @globalActor เป็นโปรโตคอลสำหรับสร้างแอ็กเตอร์ระดับโลกของคุณเอง ตัวอย่างเช่น คุณสามารถสร้าง @BackgroundActor สำหรับทำงานโค้ดบนเธรดเบื้องหลังหากสถาปัตยกรรมโปรเจกต์ต้องการ

สามารถใช้ @MainActor โดยไม่มี async/await ได้หรือไม่?

ได้ ฟังก์ชันซิงโครนัส กับ @MainActor ก็ทำงานบนเธรดหลักเช่นกัน อย่างไรก็ตาม คุณค่าหลักของ @MainActor เปิดเผยกับ async/await เมื่อฟังก์ชันแบบอะซิงโครนัสกลับมาทำงานบนเธรดหลักโดยอัตโนมัติโดยไม่ต้องสลับด้วยตนเองผ่าน DispatchQueue.main

วิธียกเลิกงาน @MainActor?

Task.cancel() ทำงานกับงาน @MainActor เช่นเดียวกับงานปกติ งาน @MainActor สามารถตรวจสอบ Task.isCancelled หรือโยน CancellationError เมื่อยกเลิก เธรดหลักจะไม่ถูกบล็อก — งานเพียงหยุดทำงานที่จุดระงับที่ใกล้ที่สุด

จะเกิดอะไรขึ้นหากเรียก @MainActor จากเธรดเบื้องหลัง?

คอมไพเลอร์ รับประกันความปลอดภัย: หากคุณเรียกฟังก์ชัน @MainActor จากบริบทเบื้องหลัง คอมไพเลอร์จะชี้ให้เห็นข้อผิดพลาด สำหรับการเรียกแบบอะซิงโครนัส เพียงทำเครื่องหมายโค้ดที่เรียกด้วย await และตัวดำเนินการจะสลับไปยังเธรดหลักเอง สำหรับการเรียกแบบซิงโครนัส จำเป็นต้องสลับอย่างชัดเจนผ่าน MainActor.run

สรุป

  • @MainActor — แอ็กเตอร์ระดับโลกของ Swift ที่รับประกันการทำงานบนเธรดหลัก
  • การตรวจสอบของคอมไพเลอร์ กำจัดข้อผิดพลาดความปลอดภัยของ UI ทั้งคลาส
  • คำอธิบายประกอบทั้งคลาส จะวางเมธอดทั้งหมดบนเธรดหลักโดยอัตโนมัติ
  • MainActor.run — การสลับอย่างชัดเจนสำหรับโค้ดดั้งเดิมและบริบทซิงโครนัส
  • แตกต่างจาก DispatchQueue.main @MainActor ไม่สร้างคลosure และใช้การสลับตัวดำเนินการ
  • การคำนวณหนัก ไม่ควรทำงานภายใต้ @MainActor เพื่อหลีกเลี่ยงการค้างของ UI
  • การสืบทอดบริบทของแอ็กเตอร์ ทำให้สายการเรียกแบบอะซิงโครนัสง่ายขึ้นและทำให้โค้ดสอดคล้อง คาดเดาได้ และปลอดภัยสำหรับ UI

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

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

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

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