مقدمة في البرمجة غير المتزامنة في Swift
يجب أن تكون تطبيقات الهواتف المحمولة وأجهزة الكمبيوتر المكتبية الحديثة سريعة الاستجابة وسريعة. لا أحد يحب أن تتجمد واجهة المستخدم عند تحميل البيانات من الشبكة أو معالجة ملف كبير. وهنا يأتي دور البرمجة غير المتزامنة. في Swift، بدءًا من الإصدار 5.5، ظهرت مجموعة أدوات قوية وبديهية للعمل مع عدم التزامن — async/await و Task و Actor.
في هذا الدليل، سنقوم بتحليل أساسيات البرمجة غير المتزامنة في Swift لنظامي iOS و macOS. ستتعلم كيفية كتابة كود غير متزامن بدون استدعاءات رجعية (callback hell)، وإدارة الخيوط بشكل صحيح، وتجنب حالات السباق (race conditions). المادة موجهة للمطورين المبتدئين الذين هم على دراية بالفعل بأساسيات بناء جملة Swift.
ما هي البرمجة غير المتزامنة ولماذا هي مطلوبة؟
تخيل أن تطبيقك هو طباخ في المطبخ. إذا انتظر الطباخ حتى يغلي الغلاية ولم يفعل شيئًا، فسيتأخر الغداء. تسمح البرمجة غير المتزامنة لـ «الطباخ» (الخيط الرئيسي) بوضع الغلاية، وبدون انتظار غليانها، يبدأ في تقطيع السلطة. عندما تغلي الغلاية، سيعود إليها الطباخ.
في سياق iOS، هذا يعني أن العمليات الثقيلة (تحميل الصورة، تحليل JSON، العمل مع قاعدة البيانات) تتم في الخلفية، دون حظر الخيط الرئيسي المسؤول عن تحديث الواجهة. إذا لم يتم ذلك، فسوف «يتجمد» التطبيق وسيتم إغلاقه قسريًا من قبل النظام قريبًا.
مشكلة النهج القديم: الاستدعاءات الرجعية و GCD
قبل ظهور async/await، كان المطورون يستخدمون Grand Central Dispatch (GCD) والإغلاقات (الاستدعاءات الرجعية). أدى هذا إلى ما يسمى بـ «جحيم الاستدعاءات الرجعية» — كتل متداخلة من الكود كان من الصعب قراءتها وتصحيح أخطائها وصيانتها.
// النمط القديم مع GCD والإغلاقاتfunc fetchUserData(completion: @escaping (User?) -> Void) { DispatchQueue.global().async { // عملية طويلة let user = User(name: "إيفان") DispatchQueue.main.async { completion(user) } }}
fetchUserData { user in guard let user = user else { return } fetchUserPosts(userId: user.id) { posts in // استدعاء رجعي متداخل آخر print(posts) }}تقدم Swift الحديثة حلاً أكثر أناقة بكثير.
أساسيات التزامن في Swift: async/await
إعلان دالة غير متزامنة
لجعل الدالة غير متزامنة، أضف الكلمة المفتاحية async بعد قائمة المعاملات. لاستدعاء مثل هذه الدالة، تحتاج إلى استخدام الكلمة المفتاحية await.
// دالة غير متزامنةfunc fetchImage(from url: URL) async throws -> UIImage { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw URLError(.badServerResponse) } return image}
// استدعاء الدالةTask { do { let image = try await fetchImage(from: url) self.imageView.image = image } catch { print("خطأ في التحميل: \\(error)") }}لاحظ: URLSession.shared.data(from:) هي دالة غير متزامنة جاهزة من Apple. نحن فقط نستدعيها مع await، ويتم تحرير الخيط لمهام أخرى أثناء تحميل البيانات.
الاستدعاءات المتسلسلة والمتوازية
يمكنك استدعاء الدوال غير المتزامنة بشكل متسلسل أو متوازي.
// استدعاء متسلسل: الطلب الثاني ينتظر الأولlet user = try await fetchUser(id: 1)let posts = try await fetchPosts(for: user.id)
// استدعاء متوازي: كلا الطلبين يتم تنفيذهما في وقت واحدasync let user = fetchUser(id: 1)async let posts = fetchPosts(for: 1)let (resultUser, resultPosts) = try await (user, posts)في الوضع المتوازي، يتم تشغيل كلا الطلبين في وقت واحد، مما