Actor เป็นชนิดที่ถูกนำเสนอใน Swift 5.5+ ซึ่งแก้ปัญหาการแข่งข้อมูลในระดับภาษา ต่างจากการล็อกด้วยตนเองและคิว DispatchQueue actor จะแยกสถานะของมันโดยอัตโนมัติและซิงโครไนซ์การเข้าถึงมัน ซึ่งหมายความว่าสองเธรดไม่สามารถแก้ไขพร็อพเพอร์ตี้เดียวกันของชนิด actor พร้อมกันได้ ซึ่งช่วยกำจัดสภาวะแข่งขันโดยไม่ต้องใช้ความพยายามเพิ่มเติมจากนักพัฒนา กลไกนี้มีพื้นฐานมาจากแนวคิดของการแยก actor ซึ่งคอมไพเลอร์จะติดตามการเข้าถึงพร็อพเพอร์ตี้และเมธอดของ actor ตาม Apple, 2025 โมเดล actor เป็นส่วนพื้นฐานของระบบการทำงานพร้อมกันของ Swift
ประเด็นสำคัญ
Actor เป็นชนิดอ้างอิง คล้ายกับคลาส แต่มีการป้องกันอัตโนมัติต่อการแข่งข้อมูล มันถูกนำเสนอใน Swift 5.5 ซึ่งเป็นส่วนหนึ่งของระบบการทำงานพร้อมกันควบคู่ไปกับ async/await และ Task Actor รับประกันว่าสถานะที่เปลี่ยนแปลงได้ของมันจะไม่ถูกอ่านหรือเขียนพร้อมกันจากเธรดที่แตกต่างกันโดยไม่มีการซิงโครไนซ์ที่ชัดเจน
ในการประกาศ actor ให้ใช้คำหลัก actor ตามด้วยวงเล็บปีกกาที่มีสมาชิกของมัน ในทางไวยากรณ์ actor มีลักษณะคล้ายคลาส แต่พฤติกรรมของมันแตกต่างอย่างสิ้นเชิง
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
คอมไพเลอร์จะแยกพร็อพเพอร์ตี้และเมธอดทั้งหมดของ actor โดยอัตโนมัติเพื่อให้เข้าถึงได้เฉพาะภายใน บริบทของ actor เท่านั้น เมื่อพยายามเข้าถึง balance จากภายนอก actor คอมไพเลอร์จะสร้างข้อผิดพลาดหากการเรียกไม่ได้ถูกทำเครื่องหมายเป็น async
การแยกข้อมูล เป็นแนวคิดหลักของ actor Actor รับประกันการเข้าถึงสถานะของมันแบบเอกสิทธิ์ซึ่งกันและกันผ่านกลไกของผู้ดำเนินการ actor แต่ละ actor มีผู้ดำเนินการของตัวเองที่ประมวลผลการเข้าถึงสมาชิกที่ถูกแยกทั้งหมดตามลำดับ
เมื่อโค้ดภายนอก actor เรียกเมธอดของมัน การเรียกจะถูกวางในคิวของ ผู้ดำเนินการ actor Actor ดำเนินการเพียงงานเดียวในแต่ละครั้ง รับประกันว่าไม่มีการแข่งกัน หากสองเธรดเรียก deposit พร้อมกัน การเรียกที่สองจะรอให้การเรียกแรกเสร็จสมบูรณ์
let account = BankAccount(initialBalance: 1000.0)
// การเรียกแบบอะซิงโครนัส — จำเป็นจากภายนอก actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
ทุกการเรียกเมธอดของ actor จำเป็นต้องมี await เพราะ actor อาจกำลังยุ่งกับงานอื่น นี่ไม่ใช่บั๊ก แต่เป็นการออกแบบโดยเจตนาที่ป้องกันการแข่งกัน Swift ทำให้ getter และ setter ของพร็อพเพอร์ตี้ของ actor เป็น async โดยปริยาย ดังนั้นการอ่านพร็อพเพอร์ตี้ก็จำเป็นต้องมี await เช่นกัน
Actor และคลาสเป็นชนิดอ้างอิงทั้งคู่ แต่พฤติกรรมของพวกมันในสภาพแวดล้อมแบบหลายเธรดแตกต่างกันอย่างมาก คลาสไม่ให้การป้องกันอัตโนมัติใด ๆ ต่อการแข่งกัน ในขณะที่ actor ฝังมันไว้ในระดับคอมไพเลอร์ผ่านระบบชนิด
| ลักษณะ | Actor | คลาส |
|---|---|---|
| การป้องกันการแข่ง | อัตโนมัติ ในระดับคอมไพเลอร์ | ต้องการการซิงโครไนซ์ด้วยตนเอง |
| การเข้าถึงพร็อพเพอร์ตี้ | ผ่าน await จากภายนอกเท่านั้น | โดยตรง ไม่ต้องซิงโครไนซ์ |
| การสืบทอด | จาก actor อื่นเท่านั้น | แบบคลาสมาตรฐาน |
| ความสอดคล้องโปรโตคอล | สามารถสอดคล้องกับโปรโตคอลได้ | มาตรฐาน |
| ประสิทธิภาพ | โอเวอร์เฮดต่ำกับการแยก | เร็วกว่าไม่มีการซิงโครไนซ์ |
Actor สามารถสืบทอดจาก actor อื่นเท่านั้นและไม่สามารถสืบทอดจากคลาสได้ นี่เป็นความตั้งใจเพราะคลาสขาดกลไก การแยกของ actor และการผสมชนิดจะทำให้การรับประกันความปลอดภัยเสียหาย
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
การเรียกแบบอะซิงโครนัส เป็นกลไกสำหรับโต้ตอบกับ actor จากโค้ดภายนอก เนื่องจาก actor แยกสถานะของมัน การเข้าถึงสมาชิกของมันจากภายนอกจึงจำเป็นต้องมี await ซึ่งช่วยให้ Swift รับประกันว่าโค้ดที่เรียกไม่บล็อกเธรดและ actor สามารถประมวลผลคำขออื่น ๆ ได้
นอกจากชนิดของ actor ที่ประกาศแล้ว Swift รองรับ actor ทั่วโลก — คุณลักษณะ @MainActor ซึ่งทำเครื่องหมายคลาส พร็อพเพอร์ตี้ หรือเมธอดว่าสามารถดำเนินการบนเธรดหลักได้ ซึ่งมีประโยชน์อย่างยิ่งสำหรับโค้ด UI ในแอปพลิเคชัน iOS
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// โค้ดนี้รับประกันว่าจะทำงานบนเธรดหลัก
title = "New Title"
}
}
การใช้ @MainActor ช่วยขจัดความจำเป็นในการเรียก DispatchQueue.main.async ด้วยตนเอง ทำให้โค้ดสะอาดและปลอดภัยยิ่งขึ้น คอมไพเลอร์ตรวจสอบว่าการสลับไปยังเธรดหลักเกิดขึ้นอย่างถูกต้อง
Nonisolated เป็นคำหลักที่อนุญาตให้ทำเครื่องหมายเมธอดหรือพร็อพเพอร์ตี้ที่คำนวณของ actor ว่าไม่ได้ถูกแยก สมาชิกดังกล่าวไม่สามารถเข้าถึงสถานะที่ถูกแยกของ actor แต่สามารถเรียกได้โดยไม่ต้อง await จากภายนอก actor
เมธอด nonisolated มีประโยชน์สำหรับการคำนวณที่ไม่ขึ้นอยู่กับสถานะที่เปลี่ยนแปลงได้ของ actor ตัวอย่างเช่น เมธอด formatBalance ไม่ได้เข้าถึง balance โดยตรง แต่เพียงจัดรูปแบบค่าที่ส่งมา — เมธอดเช่นนี้ปลอดภัยที่จะทำให้เป็น nonisolated
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
สมาชิก nonisolated ดำเนินการแบบซิงโครนัสและไม่ต้องการ await อย่างไรก็ตาม พวกมันไม่สามารถอ่านพร็อพเพอร์ตี้ที่ถูกแยกของ actor ได้โดยตรง หากเมธอด nonisolated ต้องการค่าจาก actor จะต้อง ส่งเป็นพารามิเตอร์
Reentrancy เป็นกลไกที่อนุญาตให้เข้า actor ซ้ำในระหว่างรอการเรียกแบบอะซิงโครนัส หากไม่มี reentrancy actor อาจตายแบบตลอดไปหากหนึ่งในเมธอดของมันรออีกเมธอดที่รอเมธอดแรก
เมื่อโค้ดภายใน actor ดำเนินการ await actor จะระงับงานปัจจุบันและสามารถ ประมวลผลงานอื่น ที่อยู่ในคิวได้ หลังจาก await เสร็จสมบูรณ์ งานจะกลับมาดำเนินการต่อ สิ่งนี้ป้องกันการตายแบบแต่ต้องใช้ความระมัดระวัง: สถานะของ actor ระหว่างจุด await อาจเปลี่ยนแปลง
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — จุดเข้าซ้ำ
let result = await fetchData(id: id)
// แคชอาจเปลี่ยนแปลงหลังจาก await — ตรวจสอบอีกครั้ง
cache[id] = result
return result
}
}
นักพัฒนาต้องคำนึงถึงการเข้าซ้ำและตรวจสอบสถานะของ actor หลังจากจุด await ข้อผิดพลาดทั่วไปคือการสันนิษฐานว่าการแยกของ actor ยังคงอยู่ตลอด จุดพัก แบบอะซิงโครนัส ในทางปฏิบัติ ระหว่าง await และคำสั่งถัดไป สถานะอาจแตกต่างจากที่คาดไว้
คำถามที่พบบ่อย
Actor แยกสถานะของมันจากการแข่งข้อมูลโดยอัตโนมัติ จำเป็นต้องมี await สำหรับการเข้าถึงจากภายนอก คลาสไม่มีการป้องกันเช่นนี้ — นักพัฒนาต้องรับผิดชอบการซิงโครไนซ์ผ่านการล็อกหรือคิว Actor สืบทอดจาก actor เท่านั้น คลาสสืบทอดจากคลาส
ได้ actor สามารถสืบทอดจาก actor อื่น คลาสย่อย จะได้รับพร็อพเพอร์ตี้และเมธอดที่ถูกแยกทั้งหมดของพ่อแม่ Actor ไม่สามารถสืบทอดจากคลาสได้เพราะคลาสไม่มีกลไกการแยกของ actor ในระดับคอมไพเลอร์
บริบท actor-isolated เป็นพื้นที่โค้ดที่อนุญาตให้เข้าถึงสถานะที่เปลี่ยนแปลงได้ของ actor โดยตรง ภายในเมธอดของ actor ที่ถูกทำเครื่องหมายว่าแยกไว้ (โดยค่าเริ่มต้น) สามารถอ่านและเขียนพร็อพเพอร์ตี้ได้โดยไม่ต้อง await คอมไพเลอร์ตรวจสอบขอบเขตการแยก
ข้อมูลจาก actor ถูกส่งผ่าน เมธอด async ที่คืนค่าชนิด Sendable หรือผ่านเมธอด nonisolated ที่รับค่าเป็นพารามิเตอร์ นอกจากนี้ยังสามารถสร้างพร็อพเพอร์ตี้ async ที่คืนภาพรวมของสถานะของ actor เป็นโครงสร้าง Sendable
ได้ actor สามารถสอดคล้องกับโปรโตคอลได้ หากโปรโตคอลมี ข้อกำหนดที่ถูกแยก (actor-isolated) ข้อกำหนดเหล่านั้นจะกลายเป็นถูกแยกโดย actor โดยอัตโนมัติ สำหรับเมธอดแบบอะซิงโครนัสในโปรโตคอล สามารถระบุได้ว่าต้องเรียกบน actor เฉพาะโดยใช้เครื่องหมาย isolated
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ