{"id":"l-sw-4-1","courseId":"swift-essentials","lang":"ar","kind":"lesson","summary":"اكتب دوال async، وابدأ العمل من SwiftUI عبر .task، وشغّل الاستدعاءات بالتوازي باستخدام async let ومجموعات المهام، واقرأ أخطاء سباق البيانات في Swift 6 المتعلّقة بالـ actors والـ Sendable.","html":"<p>يوشك Pocket Budget أن يستدعي خادمًا للحصول على أسعار الصرف. قد يستغرق الطلب 200 ميلي ثانية، أو ثماني ثوانٍ في القطار. لو جلس الـ main thread، الذي يرسم الواجهة ويتعامل مع اللمسات، ينتظره، لتجمّد التطبيق وربما أنهاه iOS في النهاية. نموذج الـ concurrency (التزامن) في Swift يتيح لك كتابة كود ينتظر دون أن يحجب، بأسلوب يُقرأ من الأعلى إلى الأسفل، وتضيف Swift 6 فحوصات في الـ compiler تثبت أن الكود الذي يعمل بالتوازي خالٍ من سباقات البيانات.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">async وawait</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">RateService.swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Foundation</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> RateService</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fetchRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> base: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">to</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> target: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> throws</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Decimal {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Task.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">sleep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">milliseconds</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">300</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// stands in for a network call</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> target </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"EUR\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.92</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.79</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">async</code> في التوقيع تعني «هذه الدالة قد تتوقّف مؤقتًا». وكل استدعاء لها يُعلَّم بـ <code dir=\"ltr\">await</code>، التي تعلّم <strong>نقطة تعليق</strong> (suspension point): قد تتوقّف الدالة هناك، ويذهب الـ thread لأداء عمل آخر (مثل تمرير قائمة)، ثم تستأنف الدالة حين تكون النتيجة جاهزة. وتجتمع <code dir=\"ltr\">async</code> مع <code dir=\"ltr\">throws</code>، فيكون الاستدعاء <code dir=\"ltr\">try await</code>. في الدرس التالي يصبح <code dir=\"ltr\">Task.sleep</code> طلبًا حقيقيًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">بدء العمل غير المتزامن من SwiftUI</h2>\n<p>الدوال غير المتزامنة لا تُستدعى إلا من سياق غير متزامن. ويعطيك SwiftUI واحدًا عبر الـ modifier المسمّى <code dir=\"ltr\">.task</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@MainActor</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@Observable</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">final</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> BudgetStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses: [Expense] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> []</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> euroRate: Decimal</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> isLoadingRate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rates </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> RateService</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> loadEuroRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        isLoadingRate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        defer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { isLoadingRate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        do</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            euroRate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rates.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">fetchRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">to</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"EUR\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            euroRate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> nil</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> RateBanner</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store: BudgetStore</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Group</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">            if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store.isLoadingRate {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ProgressView</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> if</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store.euroRate {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"1 USD = </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">rate.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">formatted</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> EUR\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Rate unavailable\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">task</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">            await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">loadEuroRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>تبدأ <code dir=\"ltr\">.task</code> حين يظهر الـ view وتُ<strong>لغى</strong> تلقائيًا حين يختفي، فالمستخدم الذي يغادر الشاشة لا يترك طلبًا يعمل في الخلفية. أما للنقر على زر، فغلّف الاستدعاء في <code dir=\"ltr\">Task { await store.loadEuroRate() }</code>، الذي يبدأ عملًا جديدًا على المستوى الأعلى. وتعيد <code dir=\"ltr\">defer</code> ضبط علامة التحميل أيًّا كانت طريقة خروج الدالة.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ concurrency المهيكلة: أبناء متوازون</h2>\n<p>طلبان مستقلّان لا يجب أن ينتظر أحدهما الآخر. <code dir=\"ltr\">async let</code> تبدأ العمل فورًا وتتيح لك جمعه لاحقًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> loadBothRates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">using</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> service: RateService) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> throws</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (eur: Decimal, gbp: Decimal) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> eur </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> service.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">fetchRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">to</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"EUR\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> gbp </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> service.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">fetchRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">to</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"GBP\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (eur, gbp)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>يعمل الطلبان في الوقت نفسه، فيستغرق هذا نحو 300 ms بدل 600. ولعدد من المهام لا تعرفه إلا وقت التشغيل، استخدم مجموعة مهام (task group):</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> loadRates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> codes: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">using</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> service: RateService) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> throws</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal] {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withThrowingTaskGroup</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, Decimal).</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { group </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> code </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> codes {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            group.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">addTask</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                (code, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> service.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">fetchRate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">to</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: code))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rates: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        for</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> try</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (code, rate) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> group {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            rates[code] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rate</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rates</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>هذه هي الـ concurrency <strong>المهيكلة</strong> (structured concurrency): المهام الأبناء تعيش داخل نطاق أبيها. لا تستطيع الدالة أن تعود ما دام ابن ما زال يعمل، وإن رمى أحد الأبناء خطأً أُلغي الباقون، وإلغاء الأب يلغي كل الأبناء. لاحظ أن كل ابن <em>يعيد</em> نتيجته والأب يجمّع القاموس. الأبناء لا يكتبون أبدًا في state مشتركة.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">سباقات البيانات والـ compiler في Swift 6</h2>\n<p>يحدث <strong>سباق البيانات</strong> (data race) حين يصل threadان إلى الذاكرة نفسها في الوقت نفسه ويكتب أحدهما على الأقل. والنتيجة بيانات تالفة أو عطل يظهر مرة في كل ألف تشغيل. يحوّل وضع اللغة Swift 6 فحوصات ذلك إلى أخطاء ترجمة، منظّمة حول مفهوم <strong>العزل</strong> (isolation): كل قطعة من الـ state القابلة للتغيير تنتمي إلى نطاق واحد بالضبط، ولا يلمسها مباشرةً إلا الكود الموجود في ذلك النطاق.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 240\" role=\"img\" aria-labelledby=\"l-sw-4-1-t1\"><defs><marker id=\"arrow-l-sw-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sw-4-1-t1\">ثلاثة نطاقات عزل. الـ main actor يحمل الـ views والمخزن BudgetStore. والـ actor المسمّى RateCache يحمل الأسعار المخزّنة مؤقتًا. والمهام الأبناء تجلب الأسعار بالتوازي. لا تعبر بين النطاقات إلا قيم Sendable، مثل سعر من النوع Decimal، وكل عبور هو await.</title>\n  <rect class=\"d-box-primary\" x=\"15\" y=\"30\" width=\"220\" height=\"150\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"125\" y=\"58\" text-anchor=\"middle\">@MainActor</text>\n  <text class=\"d-code\" x=\"125\" y=\"95\" text-anchor=\"middle\">Views</text>\n  <text class=\"d-code\" x=\"125\" y=\"125\" text-anchor=\"middle\">BudgetStore</text>\n  <rect class=\"d-box-accent\" x=\"465\" y=\"30\" width=\"220\" height=\"150\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"575\" y=\"58\" text-anchor=\"middle\">actor RateCache</text>\n  <text class=\"d-code\" x=\"575\" y=\"105\" text-anchor=\"middle\">rates: [String: Decimal]</text>\n  <rect class=\"d-box-success\" x=\"265\" y=\"70\" width=\"170\" height=\"70\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"100\" text-anchor=\"middle\">مهام أبناء</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"122\" text-anchor=\"middle\">تجلب بالتوازي</text>\n  <path class=\"d-arrow\" d=\"M265 90 L237 90\" marker-end=\"url(#arrow-l-sw-4-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M435 90 L463 90\" marker-end=\"url(#arrow-l-sw-4-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"350\" y=\"215\" text-anchor=\"middle\">لا يعبر الحدود إلا قيم Sendable، وكل عبور هو await</text>\n</svg><figcaption>نطاقات العزل في Pocket Budget</figcaption></figure>\n<p>ثلاث أدوات تغطّي معظم الحالات في أي تطبيق:</p>\n<p><strong>الـ main actor.</strong> يعزل <code dir=\"ltr\">@MainActor</code> النوع على الـ main thread. حالة الواجهة تنتمي إلى هناك، ولهذا عُلّم <code dir=\"ltr\">BudgetStore</code> بـ <code dir=\"ltr\">@MainActor</code> في الكود أعلاه: الـ views التي تقرؤه تعمل على الـ main actor، والآن يضمن الـ compiler ألّا يكتب فيه أي شيء آخر. مشاريع التطبيقات الجديدة في Xcode 26 وما بعده تضبط إعداد البناء <strong>Default Actor Isolation</strong> على <code dir=\"ltr\">MainActor</code>، فيكون كود تطبيقك معزولًا على الـ main actor ما لم تقل غير ذلك. إنه افتراض معقول: معظم كود التطبيقات هو كود واجهة.</p>\n<p><strong>الـ actors.</strong> الـ <code dir=\"ltr\">actor</code> نوع مرجعي يحمي الـ state الخاصة به بتنفيذ استدعاء واحد في كل مرة. والكود خارج الـ actor يجب أن يستخدم <code dir=\"ltr\">await</code> للوصول إليه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">actor</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> RateCache</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rates: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> rate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> code: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Decimal</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        rates[code]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> save</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rate: Decimal, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> code: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        rates[code] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rate</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// From anywhere: await cache.save(0.92, for: \"EUR\")</span></span></code></pre></div>\n<p><strong>القيم من نوع Sendable.</strong> يكون النوع <code dir=\"ltr\">Sendable</code> (قابلًا للإرسال) حين يكون تمريره بين النطاقات آمنًا. أنواع القيم المبنية من أجزاء Sendable (<code dir=\"ltr\">String</code> و<code dir=\"ltr\">Decimal</code> والـ struct المسمّى <code dir=\"ltr\">Expense</code>) تستوفي ذلك تلقائيًا، وكذلك الـ actors والـ classes المعزولة على الـ main actor. أما الـ class العادي الذي فيه خصائص <code dir=\"ltr\">var</code> فلا يستوفيه، ويمنعك الـ compiler من مشاركته بين المهام.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>مشاركة كائن قابل للتغيير بين المهام الأبناء</span></p>\n<p>كتابة <code dir=\"ltr\">group.addTask { tally.total += amount }</code>، حيث <code dir=\"ltr\">tally</code> كائن من class عادي، تفشل في الترجمة في Swift 6: يُبلغ الـ compiler أن تمرير الـ closure “risks causing data races”، أو، مع العزل الافتراضي على الـ main actor، أن خاصية معزولة على الـ main actor لا يمكن تعديلها من سياق غير معزول. لا تُسكت الخطأ. اجعل كل ابن يعيد قيمته واجمع النتائج في الأب، أو انقل الـ state إلى actor.</p>\n</aside>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>جعلها في المتناول</span></p>\n<p>قدّمت Swift 6.2 مجموعة خيارات يسمّيها Xcode <strong>Approachable Concurrency</strong>. معها، تعمل الدالة غير المتزامنة المعلّمة بـ <code dir=\"ltr\">nonisolated</code> على الـ actor الخاص بمن استدعاها ما لم تعلّمها بـ <code dir=\"ltr\">@concurrent</code>، ما يعني أن معظم كود التطبيق يبقى على الـ main actor ولا ينتقل عنه إلا العمل الذي تختاره أنت. سترى هذه الإعدادات في إعدادات البناء لأي مشروع جديد؛ اتركها مفعّلة.</p>\n</aside>\n<p>هذه هي المفردات: <code dir=\"ltr\">async</code> و<code dir=\"ltr\">await</code> والمهام والـ actors و<code dir=\"ltr\">Sendable</code>. لا تحتاج إلى إتقان كل قاعدة دفعة واحدة؛ اقرأ كل خطأ من الـ compiler كسؤال عن النطاق الذي يملك البيانات. بعد ذلك تستبدل <code dir=\"ltr\">Task.sleep</code> التمثيلية بطلب حقيقي وتفكّ ترميز الـ JSON الذي يعيده.</p>\n","toc":[{"id":"sec-1","text":"async وawait","level":2},{"id":"sec-2","text":"بدء العمل غير المتزامن من SwiftUI","level":2},{"id":"sec-3","text":"الـ concurrency المهيكلة: أبناء متوازون","level":2},{"id":"sec-4","text":"سباقات البيانات والـ compiler في Swift 6","level":2}],"takeaways":["<code dir=\"ltr\">await</code> تعلّم نقطة قد تتوقّف فيها الدالة مؤقتًا؛ ويكون الـ thread حرًّا لأداء عمل آخر، مثل إبقاء الواجهة مستجيبة، أثناء الانتظار.","ابدأ العمل غير المتزامن من view عبر <code dir=\"ltr\">.task</code>، الذي يلغيه SwiftUI تلقائيًا حين يختفي الـ view.","<code dir=\"ltr\">async let</code> ومجموعات المهام تشغّل العمل بالتوازي كمهام أبناء يجب أن تنتهي، أو تُلغى، قبل أن يعود الأب.","ترفض Swift 6 ترجمة الكود الذي قد تلمس فيه مهمّتان الـ state القابلة للتغيير نفسها في الوقت ذاته؛ والـ actors والأنواع من نوع <code dir=\"ltr\">Sendable</code> هي طريقتك لإرضائها.","أبقِ حالة الواجهة على الـ main actor عبر <code dir=\"ltr\">@MainActor</code>؛ ومشاريع Xcode الجديدة تجعل العزل على الـ main actor هو الافتراضي لكود التطبيق."],"further":[{"title":"Concurrency (The Swift Programming Language)","url":"https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/"},{"title":"Swift 6 Migration Guide","url":"https://www.swift.org/migration/documentation/migrationguide/"},{"title":"Updating an app to use strict concurrency","url":"https://developer.apple.com/documentation/swift/updating-an-app-to-use-strict-concurrency"}],"quiz":[{"q":"<p>يحمّل view أسعار الصرف داخل <code dir=\"ltr\">.task { await store.loadEuroRate() }</code>. يغادر المستخدم الشاشة قبل انتهاء الطلب. ماذا يحدث؟</p>","options":[{"text":"تستمر المهمة في العمل وتحدّث المخزن لاحقًا.","why":"هذا ما تفعله مهمة غير مهيكلة <code dir=\"ltr\">Task { }</code> داخل زر. أما <code dir=\"ltr\">.task</code> فمرتبطة بعمر الـ view."},{"text":"يلغي SwiftUI المهمة، والاستدعاءات التي تنتبه للإلغاء مثل <code dir=\"ltr\">Task.sleep</code> أو طلبات URLSession ترمي خطأً.","why":"صحيح. الإلغاء تعاوني؛ والـ APIs غير المتزامنة المنضبطة تلاحظه وتتوقّف مبكرًا."},{"text":"يتعطّل التطبيق لأن الـ view لم يعد موجودًا.","why":"المهمة تحمل ما تحتاجه؛ ولا تعتمد على بقاء الـ struct الخاص بالـ view."}],"answer":1},{"q":"<p>لديك <code dir=\"ltr\">async let eur = fetch(&quot;EUR&quot;)</code> و<code dir=\"ltr\">async let gbp = fetch(&quot;GBP&quot;)</code>، وكلٌّ منهما يستغرق نحو 300 ms، يليهما <code dir=\"ltr\">try await (eur, gbp)</code>. كم يستغرق ذلك في المجمل؟</p>","options":[{"text":"نحو 300 ms، لأن الطلبين يعملان في الوقت نفسه.","why":"صحيح. كل <code dir=\"ltr\">async let</code> تبدأ مهمة ابنًا فورًا؛ وتجمع <code dir=\"ltr\">await</code> النتيجتين."},{"text":"نحو 600 ms، لأن كل <code dir=\"ltr\">await</code> تنتظر دورها.","why":"هذا ما يحدث مع استدعاءين عاديين <code dir=\"ltr\">try await</code> على التوالي. أما <code dir=\"ltr\">async let</code> فتبدأ الاثنين قبل انتظار أيٍّ منهما."},{"text":"فورًا، لأن <code dir=\"ltr\">async let</code> لا تنتظر.","why":"العمل ما زال يستغرق وقتًا؛ <code dir=\"ltr\">async let</code> تسمح فقط بتداخله."}],"answer":0},{"q":"<p>لماذا تستطيع استدعاء <code dir=\"ltr\">await cache.save(rate, for: &quot;EUR&quot;)</code> على actor من مهام كثيرة في الوقت نفسه دون سباق بيانات؟</p>","options":[{"text":"الـ actors تنسخ نفسها لكل مستدعٍ.","why":"الـ actors أنواع مرجعية ولا تُنسخ أبدًا؛ هناك instance واحد بمجموعة state واحدة."},{"text":"الـ actors تقفل التطبيق كله أثناء عملها.","why":"الـ state الخاصة بذلك الـ actor وحدها هي المحمية، ولا تنفّذ إلا مهمة واحدة في كل مرة كودًا عليه."},{"text":"الـ actor ينفّذ استدعاءً واحدًا في كل مرة على الـ state الخاصة به، فينتظر المستدعون دورهم عبر <code dir=\"ltr\">await</code>.","why":"صحيح. هذا التسلسل هو سبب حاجة كل استدعاء خارجي لـ actor إلى <code dir=\"ltr\">await</code>."}],"answer":2},{"q":"<p>أيّ نوع يكون <code dir=\"ltr\">Sendable</code> دون أن تكتب أي شيء إضافي؟</p>","options":[{"text":"‏<code dir=\"ltr\">final class</code> فيه خاصية <code dir=\"ltr\">var</code>.","why":"الـ state المشتركة القابلة للتغيير هي بالضبط ما يستبعده <code dir=\"ltr\">Sendable</code>؛ ولن يستنتجه الـ compiler."},{"text":"struct غير عام كل خصائصه المخزّنة <code dir=\"ltr\">Sendable</code>، مثل <code dir=\"ltr\">String</code> و<code dir=\"ltr\">Decimal</code>.","why":"صحيح. أنواع القيم المبنية من قيم Sendable يمكن نسخها بأمان بين المهام، فتستنتج Swift ذلك."},{"text":"أي class معلّم بـ <code dir=\"ltr\">@Observable</code>.","why":"الـ classes المراقَبة قابلة للتغيير وليست Sendable إلا إذا كانت معزولة على actor مثل <code dir=\"ltr\">@MainActor</code>."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"swift","code":"import Foundation\n\nfinal class Tally {\n    var total: Decimal = 0\n}\n\nfunc totalOfAll(_ amounts: [Decimal]) async -> Decimal {\n    let tally = Tally()\n    await withTaskGroup(of: Void.self) { group in\n        for amount in amounts {\n            group.addTask { tally.total += amount }\n        }\n    }\n    return tally.total\n}","bugLines":[11],"title":"سباق البيانات الذي ترفض Swift 6 بناءه","prompt":"<p>هذه الدالة المساعدة تجمع قائمة من المبالغ باستخدام مجموعة مهام. ستعمل بلا مشكلة في اختبار سريع، ومع ذلك يرفض الـ compiler في Swift 6 بناءها. اختر السطر الذي ينشئ سباق البيانات.</p>\n","hints":["كم مهمة ابنًا تعمل في الوقت نفسه، وما الذي تلمسه كلها؟","<code dir=\"ltr\">Tally</code> هو class عادي فيه <code dir=\"ltr\">var</code>. هل هو <code dir=\"ltr\">Sendable</code>؟","في الـ concurrency المهيكلة، يجب أن يسلّم الأبناء النتائج إلى الأب بدل الكتابة في state مشتركة."],"explanation":"<p>السطر 11 يبدأ مهمة ابنًا لكل مبلغ، وكل ابن يعدّل كائن <code dir=\"ltr\">Tally</code> نفسه. المهام الأبناء تعمل بالتوازي، فقد يقرأ اثنان منها <code dir=\"ltr\">total</code> ويكتبانها في اللحظة نفسها فيضيع أحد التحديثين. ولأن <code dir=\"ltr\">Tally</code> ليس <code dir=\"ltr\">Sendable</code>، ترفض Swift 6 الالتقاط (“passing closure as a ‘sending’ parameter risks causing data races”، أو خطأ عزل على الـ main actor حين يستخدم مشروعك العزل الافتراضي على الـ main actor).</p>\n<p>اجعل كل ابن يعيد قيمته واجمع في الأب، الذي لا ينفّذ إلا دورة واحدة من الحلقة في كل مرة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> totalOfAll</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amounts: [Decimal]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Decimal {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    await</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withTaskGroup</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { group </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amounts {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            group.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">addTask</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { amount }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> total: Decimal </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        for</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> part </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> group {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            total </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> part</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> total</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>وإن كان لا بدّ فعلًا من مشاركة الـ state، فاجعل <code dir=\"ltr\">Tally</code> من نوع <code dir=\"ltr\">actor</code> واستدعِ <code dir=\"ltr\">await tally.add(amount)</code>.</p>\n","checkLabels":{}},"words":748,"readMinutes":4}