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 يجعل بشكل ضمني دوال الحصول على الخصائص وتعيينها غير متزامنة، لذا فإن قراءة خاصية تتطلب await أيضاً.
Actor والكلاس كلاهما نوعان مرجعيان، لكن سلوكهما في بيئة متعددة الخيوط يختلف جذرياً. لا يوفر الكلاس أي حماية تلقائية ضد السباقات، بينما يدمجها actor على مستوى المترجم من خلال نظام الأنواع.
| الخاصية | Actor | كلاس |
|---|---|---|
| الحماية من السباقات | تلقائية، على مستوى المترجم | تتطلب مزامنة يدوية |
| الوصول إلى الخصائص | فقط عبر await من الخارج | مباشر، بدون مزامنة |
| الوراثة | فقط من actors أخرى | وراثة كلاس قياسية |
| توافق البروتوكولات | يمكنه التوافق مع البروتوكولات | قياسي |
| الأداء | عبء منخفض مع العزل | أسرع بدون مزامنة |
يمكن لـ actor أن يرث فقط من actor آخر ولا يمكنه الوراثة من كلاس. هذا مقصود لأن الكلاس يفتقر إلى آلية عزل actor، وخلط الأنواع من شأنه أن يكسر ضمانات الأمان.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
الاستدعاءات غير المتزامنة هي آلية التفاعل مع actor من كود خارجي. نظراً لأن actor يعزل حالته، فإن أي وصول إلى أعضائه من الخارج يتطلب await. يتيح ذلك لـ Swift ضمان أن الكود المستدعي لا يحجب الخيط ويمكن لـ actor معالجة الطلبات الأخرى.
بالإضافة إلى أنواع actor المعلنة، يدعم Swift الـ actors العالمية — السمة @MainActor، التي تميز الكلاسات أو الخصائص أو الطرق بأنها قابلة للتنفيذ على الخيط الرئيسي. هذا مفيد بشكل خاص لكود واجهة المستخدم في تطبيقات 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 عبر طرق غير متزامنة تعيد أنواع Sendable، أو عبر طرق nonisolated التي تقبل القيم كمعاملات. يمكن أيضاً إنشاء خاصية غير متزامنة تعيد لقطة من حالة actor كهيكل Sendable.
نعم، يمكن لـ actor أن يتوافق مع البروتوكولات. إذا كان البروتوكول يحتوي على متطلبات معزولة (actor-isolated)، فإنها تصبح تلقائياً معزولة لـ actor. للطرق غير المتزامنة في البروتوكولات، يمكن تحديد أنه يجب استدعاؤها على actor محدد باستخدام علامة isolated.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.