مقدمة في البرمجة غير المتزامنة في Swift
\n\nنادرًا ما تخلو تطبيقات الهواتف المحمولة الحديثة من طلبات الشبكة، أو العمل مع قواعد البيانات، أو معالجة الملفات الكبيرة. كل هذه العمليات تستغرق وقتًا، وإذا تم تنفيذها في الخيط الرئيسي، فسيتجمد واجهة التطبيق وسيحصل المستخدم على تجربة غير سارة. وهنا يأتي دور البرمجة غير المتزامنة.
\n\nقبل ظهور Swift 5.5، كان المطورون يستخدمون الإغلاقات (completion handlers) أو المكتبات الخارجية (Combine، RxSwift)، مما أدى غالبًا إلى "جحيم الاستدعاءات الراجعة" وكود يصعب قراءته. مع إطلاق النموذج الجديد Swift Concurrency (async/await)، أصبحت كتابة الكود غير المتزامن بنفس سهولة ووضوح الكود المتزامن.
\n\nفي هذا الدليل، سنشرح المفاهيم الأساسية للبرمجة غير المتزامنة في Swift من خلال أمثلة عملية. ستتعلم كيفية استخدام async/await و Task و Actor وأدوات أخرى لإنشاء تطبيقات سريعة وسريعة الاستجابة لنظامي iOS و macOS.
المفاهيم الأساسية لـ Swift Concurrency
\n\n1. Async و Await: أساس عدم التزامن
\n\nالكلمتان المفتاحيتان async و await هما قلب النموذج الجديد. async تعلن عن دالة على أنها غير متزامنة، بينما await توقف التنفيذ مؤقتًا لحين الحصول على النتيجة، دون حظر الخيط.
لننظر إلى مثال بسيط: تحميل البيانات من الشبكة.
\n\nimport Foundation\n\n// تعريف دالة غير متزامنة\nfunc fetchUserData() async throws -> String {\n let url = URL(string: "https://api.example.com/user/1")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return String(data: data, encoding: .utf8) ?? "خطأ"\n}\n\n// استدعاء الدالة غير المتزامنة\nTask {\n do {\n let userData = try await fetchUserData()\n print("بيانات المستخدم: \\(userData)")\n } catch {\n print("خطأ في التحميل: \\(error.localizedDescription)")\n }\n}\n\nلاحظ: try await يعني أننا ننتظر نتيجة قد ترمي خطأ. يبدو الكود وكأنه كود متزامن عادي، لكنه لا يحظر الخيط الرئيسي.
2. Task: نقطة الدخول إلى العالم غير المتزامن
\n\nلا يمكن استدعاء الدوال غير المتزامنة إلا من سياق غير متزامن آخر. لتشغيل عملية غير متزامنة من كود متزامن (مثل من viewDidLoad())، يتم استخدام Task.
class ViewController: UIViewController {\n \n override func viewDidLoad() {\n super.viewDidLoad()\n \n // تشغيل مهمة غير متزامنة\n Task {\n let data = await loadData()\n updateUI(with: data)\n }\n }\n \n func loadData() async -> String {\n // محاكاة تحميل طويل\n try? await Task.sleep(nanoseconds: 2_000_000_000)\n return "البيانات المحملة"\n }\n \n func updateUI(with data: String) {\n print("تحديث واجهة المستخدم: \\(data)")\n }\n}\n\nمهم: Task ينشئ مهمة غير متزامنة جديدة يتم تنفيذها في خيط خلفية. لتحديث واجهة المستخدم، يجب العودة إلى الخيط الرئيسي - بشكل افتراضي، يقوم await بذلك تلقائيًا.
3. Actor: الحماية من سباق البيانات
\n\nعند العمل مع تعدد الخيوط، غالبًا ما تظهر مشكلة سباق البيانات (data race)، حيث تقوم عدة خيوط بتعديل متغير واحد في نفس الوقت. تقدم Swift حلاً أنيقًا - Actor.
Actor هو نوع مرجعي يضمن أن خيطًا واحدًا فقط يمكنه الوصول إلى حالته القابلة للتعديل في أي لحظة.
actor Counter {\n private var value = 0\n \n func increment() {\n value += 1\n }\n \n func getValue() -> Int {\n return value\n }\n}\n\n// الاستخدام\nlet counter = Counter