Sendable هو بروتوكول Swift يحدد الأنواع الآمنة للنقل بين الخيوط. عند العمل مع التزامن (actor, async/await, Task)، يتطلب مترجم Swift أن جميع البيانات المنقولة بين السياقات المعزولة تتوافق مع Sendable. هذا يمنع النقل العرضي للأنواع غير الآمنة التي قد تؤدي إلى سباقات البيانات. يعمل البروتوكول كعقد: النوع المتوافق مع Sendable يضمن عدم وجود حالة داخلية غير متزامنة. وفقًا لـ WWDC 2021، فإن بروتوكول Sendable عنصر إلزامي عند تصميم بنية آمنة متعددة الخيوط.
الخلاصة
Sendable هو بروتوكول من المكتبة القياسية لـ Swift (SE-0302) يحدد الأنواع الآمنة للنقل بين نطاقات العزل. نطاق العزل يعني actor أو مهمة Task أو سياق @MainActor. لا يحتوي Sendable على متطلبات — إنه يعمل كعلامة أمان.
قبل Swift 5.5، كان بإمكان المطورين نقل أي كائن بين الخيوط عبر DispatchQueue، ولم يتحقق المترجم مما إذا كان آمنًا. Sendable يسد هذه الثغرة: الآن يتتبع المترجم نفسه عمليات النقل عبر الخيوط ويمنع غير الآمنة. هذا يجعل الكود المتزامن أكثر موثوقية حتى قبل تشغيله.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
في المثال، يمكن نقل UserProfile بأمان بين الخيوط لأنه هيكل بخصائص ثابتة من أنواع Sendable. سيتسبب NonSendableClass في خطأ ترجمة عند محاولة نقله عبر await أو إلى Task.
أنواع Sendable في Swift تنقسم إلى ثلاث فئات: أنواع القيمة بخصائص Sendable، والفئات النهائية بحالة غير قابلة للتغيير، والدوال/الإغلاقات الموسومة بـ @Sendable. يستنتج المترجم تلقائيًا توافق Sendable لأنواع القيمة.
Struct, enum وtuple تصبح تلقائيًا Sendable إذا كانت جميع خصائصها وقيمها المرتبطة أيضًا Sendable. هذا نهج تحفظي: إذا كانت خاصية واحدة على الأقل غير متوافقة مع Sendable، فلن يتم التعرف على النوع بالكامل كآمن.
| النوع | Sendable | الشرط |
|---|---|---|
| Struct | نعم (بدون تصريح صريح) | جميع الخصائص Sendable |
| Enum | نعم (بدون تصريح صريح) | جميع القيم المرتبطة Sendable |
| Final class | نعم (مع تصريح صريح) | جميع خصائص let هي Sendable، لا يوجد var |
| Non-final class | لا | لا يمكن أن تكون Sendable بسبب الوراثة |
جميع الأنواع المضمنة في Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — تتوافق مع Sendable. هذا يجعل تركيب الأنواع آمنًا افتراضيًا. يحتاج المطور فقط إلى مراقبة الفئات المخصصة.
@unchecked Sendable هي آلية تسمح للفئة بتصريح نفسها صراحةً على أنها Sendable، متجاوزة فحوصات المترجم. يتحمل المطور مسؤولية أمان الخيوط لهذه الفئة. هذا مفيد لجسور Objective-C والهياكل المحسّنة.
يُستخدم @unchecked Sendable عندما تضمن الفئة داخليًا الأمان من خلال الأقفال أو العمليات الذرية، لكن المترجم لا يمكنه التحقق من ذلك بشكل ثابت. على سبيل المثال، فئة بها os_unfair_lock أو pthread_mutex_t — أمان خيوطها مضمون بالكود، لكن Swift لا يرى ذلك.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
يجب استخدام @unchecked Sendable بحذر. إنها إشارة صريحة للمطورين الآخرين: «لقد تحققت، هذا آمن». خطأ في التنفيذ يمكن أن يؤدي إلى سباقات يصعب اكتشافها. قبل استخدام @unchecked، تأكد من أن النوع لا يمكن إعادة كتابته كنوع قيمة.
Actor و Sendable وجهان لعملة واحدة. Actor يعزل حالته، ولكن لتبادل البيانات مع العالم الخارجي يجب أن يعيد أنواع Sendable. إذا أعادت دالة actor نوعًا غير Sendable، يصدر المترجم تحذيرًا أو خطأ.
عندما يرسل actor بيانات إلى كود خارجي، تعبر هذه البيانات حدود العزل. Sendable يضمن أن المستلم يمكنه استخدامها بأمان خارج actor. يبقى actor نفسه معزولًا — حالته الداخلية لا يتم كشفها.
struct AccountSnapshot: Sendable {
let id: UUID
let balance: Double
let lastUpdated: Date
}
actor BankActor {
private var balance: Double = 0
func snapshot() async -> AccountSnapshot {
return AccountSnapshot(
id: UUID(),
balance: balance,
lastUpdated: Date()
)
}
}
AccountSnapshot هو هيكل Sendable يحتوي فقط على خصائص let من أنواع Sendable. هذا النهج هو أفضل ممارسة لاستخراج البيانات من actor. لقطة الحالة تُنقل بالقيمة، ولا يفقد actor السيطرة على حالته.
@Sendable هي سمة للدوال والإغلاقات تضمن أن الإغلاق لا يلتقط بيانات غير Sendable بطريقة قابلة للتغيير. عندما يُنقل إغلاق إلى Task أو دالة actor، يجب أن يكون Sendable.
يتحقق المترجم من أن إغلاق @Sendable لا يلتقط مراجع قابلة للتغيير للفئات. يُسمح بالتقاط خصائص let من أنواع Sendable. التقاط متغير var من نوع مرجعي سيؤدي إلى خطأ لأنه قد يتم تنفيذ الإغلاق بالتزامن مع التغيير.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — التقاط var يغير counter، خطأ في الترجمة
performAsync { // ❌ تغيير متغير ملتقط
print(constant)
}
القاعدة بسيطة: إغلاق @Sendable يمكنه فقط التقاط البيانات التي هي نفسها Sendable ولن يتم تغييرها من الخارج. للفئات، يُسمح بالتقاط مرجع ضعيف weak self إذا لم تكن الفئة موسومة كـ Sendable. هذا يمنع دورات الاحتفاظ الكلاسيكية وسباقات البيانات.
الأسئلة الشائعة
Sendable هو علامة تخبر المترجم: «يمكن نقل هذا النوع بأمان بين الخيوط». الهياكل والثوابت عادة ما تكون Sendable افتراضيًا، الفئات لا تكون كذلك إلا إذا تم تحديد ذلك صراحةً.
أنواع القيمة (struct, enum) بخصائص Sendable، والفئات النهائية بحالة غير قابلة للتغيير، وجميع الأنواع الأساسية في Swift: Int, String, Double, Bool, Array, Dictionary, Optional — تتوافق مع Sendable تلقائيًا.
@unchecked Sendable هي طريقة لتصريح فئة كـ Sendable بدون فحوصات ثابتة من المترجم. يضمن المطور الأمان بنفسه، غالبًا من خلال الأقفال أو العمليات الذرية.
Actor يعزل حالته، ولكن عند إعادة البيانات إلى الكود الخارجي، تعبر هذه البيانات حدود العزل. Sendable يضمن أن المستلم لن يواجه سباقات عند استخدام هذه البيانات خارج actor.
أضف بروتوكول Sendable إلى فئة نهائية، جميع خصائصها ثوابت من أنواع Sendable. إذا كانت الفئة تستخدم أقفالًا، يمكنك استخدام @unchecked Sendable، لكن هذا يتطلب الحذر.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.