مقدمة في البرمجة غير المتزامنة بلغة Swift
تطبيقات Swift الحديثة (iOS/macOS) تتطلب بشكل متزايد تنفيذ عمليات طويلة - تحميل البيانات من الشبكة، العمل مع الملفات، معالجة الصور - دون حظر واجهة المستخدم. حتى وقت قريب، كان المطورون يستخدمون الإغلاقات (completion handlers) و Grand Central Dispatch (GCD)، مما أدى إلى ما يسمى "جحيم الاستدعاءات الراجعة" وجعل قراءة الكود أكثر تعقيدًا. مع إصدار Swift 5.5، قدمت شركة Apple نموذجًا جديدًا جوهريًا للبرمجة غير المتزامنة، يعتمد على الكلمات المفتاحية async و await، بالإضافة إلى الممثلين (Actors) وهياكل Task. في هذا الدليل، سنشرح جميع المفاهيم الأساسية من خلال أمثلة عملية.
1. أساسيات async/await في Swift
الكلمة المفتاحية async تشير إلى أن الدالة أو الطريقة يمكنها تعليق تنفيذها لانتظار نتيجة عملية غير متزامنة. استدعاء هذه الدالة يتم تمييزه بكلمة await. هذا يسمح بكتابة كود غير متزامن بنفس الطريقة الخطية للكود المتزامن.
// تعريف دالة غير متزامنةfunc fetchUserData(from url: URL) async throws -> Data { let (data, _) = try await URLSession.shared.data(from: url) return data}
// استخدامها في سياق مهمةTask { do { let url = URL(string: "https://api.example.com/user")! let userData = try await fetchUserData(from: url) print("تم استلام البيانات: \\(userData.count) بايت") } catch { print("خطأ في التحميل: \\(error.localizedDescription)") }}لاحظ: استدعاء await يعلق المهمة الحالية، لكنه لا يحجب الخيط. النظام نفسه يبدل السياق عندما تكون النتيجة جاهزة، مما يجعل الكود فعالاً وآمنًا.
2. الممثلون (Actors) - العمل الآمن مع الحالة
البرمجة غير المتزامنة تضيف مشكلة سباق البيانات (data race). لحلها، تم إدخال الممثلين في Swift. الممثل هو نوع مرجعي يعزل حالته ويضمن أن مهمة واحدة فقط يمكنها الوصول إلى خصائصه القابلة للتغيير في أي وقت.
actor Counter { private var value = 0 func increment() { value += 1 } func getValue() -> Int { return value }}
// استخدام الممثلlet counter = Counter()
Task { await counter.increment() let currentValue = await counter.getValue() print("القيمة الحالية: \\(currentValue)")}الكلمة المفتاحية actor تجعل جميع الطرق والخصائص (باستثناء الثابتة) غير متزامنة للوصول الخارجي تلقائيًا. هذا يسمح بتجنب الأقفال وحالات الجمود (deadlocks) التي تميز الإدارة اليدوية للأقفال (NSLock, DispatchSemaphore).
3. TaskGroup والتنفيذ المتوازي للمهام
عند الحاجة لتنفيذ عدة عمليات غير متزامنة مستقلة بالتوازي، استخدم TaskGroup. هذا هيكل يسمح بتشغيل المهام الفرعية وجمع نتائجها.
func fetchMultipleUsers(ids: [Int]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in var users: [User] = [] for id in ids { group.addTask { let url = URL(string: "https://api.example.com/users/\\(id)")! let data = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data.0) } } for try await user in group { users.append(user) } return users }}
// الاستخدامTask { let users = try? await fetchMultipleUsers(ids: [1, 2, 3, 4, 5]) print("تم تحميل المستخدمين: \\(users?.count ?? 0)")}طريقة withThrowi