{"checkpoints":{"s-sw-1":[{"q":"<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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> input </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"abc\"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Int</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(input) </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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre></div>\n","options":[{"text":"لا شيء؛ لا يُترجَم لأن <code dir=\"ltr\">Int(input)</code> قيمة optional.","why":"العامل <code dir=\"ltr\">??</code> يفكّ الـ optional بقيمة افتراضية، فيصبح التعبير <code dir=\"ltr\">Int</code> عاديًا ويُترجَم."},{"text":"يتعطّل التطبيق، لأن “abc” ليست رقمًا.","why":"<code dir=\"ltr\">Int(_:)</code> تعيد <code dir=\"ltr\">nil</code> للمدخلات السيئة بدل أن تُسقط التطبيق؛ الفكّ القسري وحده هو ما يُسقطه."},{"text":"<code dir=\"ltr\">abc5</code>","why":"لا تجمع Swift أبدًا <code dir=\"ltr\">String</code> مع <code dir=\"ltr\">Int</code> بالعامل <code dir=\"ltr\">+</code>؛ والطرفان هنا كلاهما <code dir=\"ltr\">Int</code> أصلًا."},{"text":"<code dir=\"ltr\">5</code>","why":"صحيح. <code dir=\"ltr\">Int(&quot;abc&quot;)</code> تساوي <code dir=\"ltr\">nil</code>، و<code dir=\"ltr\">?? 0</code> تضع 0 مكانها، و0 + 5 تساوي 5."}],"answer":3},{"q":"<p>لديك <code dir=\"ltr\">func convert(_ amount: Decimal, to currency: String) -&gt; Decimal</code>. أيّ استدعاء يُترجَم؟</p>","options":[{"text":"<code dir=\"ltr\">convert(12, to: &quot;EUR&quot;)</code>","why":"صحيح. تسمية المعامل الأول هي <code dir=\"ltr\">_</code> فتُحذف؛ والثاني يستخدم التسمية <code dir=\"ltr\">to</code>."},{"text":"<code dir=\"ltr\">convert(amount: 12, to: &quot;EUR&quot;)</code>","why":"<code dir=\"ltr\">amount</code> هو اسم المعامل المستخدم داخل الدالة؛ و<code dir=\"ltr\">_</code> تعني أن المستدعي لا يكتب تسمية."},{"text":"<code dir=\"ltr\">convert(12, &quot;EUR&quot;)</code>","why":"للمعامل الثاني تسمية الوسيط <code dir=\"ltr\">to</code>، وSwift تشترطها في موضع الاستدعاء."},{"text":"<code dir=\"ltr\">convert(12, currency: &quot;EUR&quot;)</code>","why":"<code dir=\"ltr\">currency</code> هو الاسم الداخلي. أما المستدعي فيجب أن يستخدم التسمية الخارجية <code dir=\"ltr\">to</code>."}],"answer":0},{"q":"<p>يجب أن تعيد <code dir=\"ltr\">func monthlyAverage(_ amounts: [Decimal]) -&gt; Decimal?</code> القيمة <code dir=\"ltr\">nil</code> لمصفوفة فارغة. أيّ سطر أول هو الطريقة المعتادة في Swift للتعامل مع ذلك؟</p>","options":[{"text":"<code dir=\"ltr\">let first = amounts[0]</code>","why":"الوصول بالفهرس في مصفوفة فارغة يُسقط التطبيق مع “Index out of range” قبل أن تستطيع إعادة أي شيء."},{"text":"<code dir=\"ltr\">if amounts.isEmpty { amounts = [0] }</code>","why":"معاملات الدوال ثوابت، فلا يُترجَم هذا؛ كما أنه سيُخفي الحالة الفارغة."},{"text":"<code dir=\"ltr\">guard amounts.count &gt; 0 else { print(&quot;empty&quot;) }</code>","why":"فرع <code dir=\"ltr\">else</code> في الـ <code dir=\"ltr\">guard</code> يجب أن يغادر النطاق، فـ <code dir=\"ltr\">print</code> وحدها خطأ ترجمة."},{"text":"<code dir=\"ltr\">guard !amounts.isEmpty else { return nil }</code>","why":"صحيح. الـ guard تنصّ على القاعدة وتخرج مبكرًا، ويستطيع باقي الدالة افتراض وجود بيانات."}],"answer":3},{"q":"<p>تمرّ على المصاريف وتريد عدّ ما يقع منها في كل تصنيف، بدءًا من قاموس فارغ <code dir=\"ltr\">var counts: [String: Int] = [:]</code>. أيّ سطر يوضع داخل الحلقة؟</p>","options":[{"text":"<code dir=\"ltr\">counts[category] += 1</code>","why":"البحث بالمفتاح يعيد <code dir=\"ltr\">Int?</code>، ولا يمكنك إضافة 1 إلى optional دون فكّه."},{"text":"<code dir=\"ltr\">counts.append(category)</code>","why":"القواميس ليس فيها <code dir=\"ltr\">append</code>؛ هذه method خاصة بالمصفوفات."},{"text":"<code dir=\"ltr\">counts[category, default: 0] += 1</code>","why":"صحيح. الصيغة <code dir=\"ltr\">default:</code> تقرأ 0 للمفتاح الجديد، وتضيف 1، وتخزّن النتيجة."},{"text":"<code dir=\"ltr\">counts.insert(category)</code>","why":"<code dir=\"ltr\">insert</code> هي method خاصة بالـ <code dir=\"ltr\">Set</code>؛ وستسجّل العضوية لا العدد."}],"answer":2},{"q":"<p>ما قيمة <code dir=\"ltr\">result</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> values </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">30</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\"> result </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> values.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">$0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 10</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">$0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">84</code>","why":"صحيح. يُبقي <code dir=\"ltr\">filter</code> العددين 12 و30، ويضاعفهما <code dir=\"ltr\">map</code> إلى 24 و60، ويجمعهما <code dir=\"ltr\">reduce</code>."},{"text":"<code dir=\"ltr\">94</code>","why":"هذا يتضمّن 5 بعد مضاعفتها إلى 10. لكن <code dir=\"ltr\">filter</code> حذفها قبل تنفيذ <code dir=\"ltr\">map</code>."},{"text":"<code dir=\"ltr\">[24, 60]</code>","why":"هذه هي القيمة بعد <code dir=\"ltr\">map</code>. ثم يجمع <code dir=\"ltr\">reduce</code> المصفوفة في رقم واحد."},{"text":"<code dir=\"ltr\">42</code>","why":"هذا مجموع 12 + 30 دون مضاعفة. خطوة <code dir=\"ltr\">map</code> تُنفَّذ قبل <code dir=\"ltr\">reduce</code>."}],"answer":0}],"s-sw-2":[{"q":"<p><code dir=\"ltr\">Expense</code> هو struct فيه <code dir=\"ltr\">var amount: Decimal</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\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> list </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Expense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</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\"> first </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> list[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">first.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 9</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(list[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].amount)</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">5</code>","why":"صحيح. قراءة <code dir=\"ltr\">list[0]</code> في <code dir=\"ltr\">first</code> تنسخ الـ struct؛ وتغيير النسخة يترك المصفوفة دون مساس."},{"text":"<code dir=\"ltr\">9</code>","why":"هذا سلوك الـ class. الـ structs تُنسخ عند الإسناد، فيكون <code dir=\"ltr\">first</code> مستقلًّا."},{"text":"لا يُترجَم، لأن عناصر المصفوفة ثوابت.","why":"<code dir=\"ltr\">list</code> معرّف بـ <code dir=\"ltr\">var</code>، وكذلك <code dir=\"ltr\">first</code>؛ لا شيء هنا ثابت."},{"text":"يتعطّل التطبيق، لأن <code dir=\"ltr\">first</code> مرجع معلّق لا يشير إلى شيء.","why":"الـ structs لا مراجع فيها لتتعلّق؛ <code dir=\"ltr\">first</code> يحمل قيمته الكاملة الخاصة."}],"answer":0},{"q":"<p>ماذا يطبع هذا الكود؟</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\">enum</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Sync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> idle</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> syncing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(progress: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Double</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> failed</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\">)</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state: Sync </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">syncing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">progress</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">switch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">case</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .idle</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Idle\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">case</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">syncing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">where</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Almost\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">case</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .syncing</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Working\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">case</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">failed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> message)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(message)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">Almost</code>","why":"هذه الحالة تتطلّب تقدّمًا أكبر من 0.5، و0.4 لا تحقّق شرط <code dir=\"ltr\">where</code>."},{"text":"<code dir=\"ltr\">Idle</code>","why":"القيمة هي <code dir=\"ltr\">.syncing</code>، فلا يمكن أن تطابقها الحالة <code dir=\"ltr\">.idle</code>."},{"text":"لا شيء، لأن لا حالة تطابق 0.4.","why":"الحالة العادية <code dir=\"ltr\">case .syncing:</code> تطابق أي تقدّم، فهناك دائمًا حالة مطابقة."},{"text":"<code dir=\"ltr\">Working</code>","why":"صحيح. يفشل شرط <code dir=\"ltr\">where</code>، فتنتقل المطابقة إلى حالة <code dir=\"ltr\">.syncing</code> التالية."}],"answer":3},{"q":"<p>الـ protocol المعرّف <code dir=\"ltr\">protocol Exportable { func csvRow() -&gt; String }</code> له خمسة أنواع متوافقة. يجب أن يحصل كلٌّ منها أيضًا على <code dir=\"ltr\">csvLine</code>، أي <code dir=\"ltr\">csvRow()</code> مع سطر جديد، دون كتابتها خمس مرات. ماذا تضيف؟</p>","options":[{"text":"متطلّبًا <code dir=\"ltr\">var csvLine: String { get }</code> في الـ protocol.","why":"المتطلّب يُلزم كل نوع بتنفيذه، وهذا هو التكرار الذي أردت تجنّبه."},{"text":"<code dir=\"ltr\">extension Exportable { var csvLine: String { csvRow() + &quot;\\n&quot; } }</code>","why":"صحيح. الـ protocol extension يعطي كل نوع متوافق التنفيذ دون أي جهد."},{"text":"class أساسيًا <code dir=\"ltr\">ExportableBase</code> ترث منه الأنواع الخمسة كلها.","why":"الـ structs والـ enums لا تستطيع الوراثة، والوراثة أثقل مما تحتاجه هذه المشكلة."},{"text":"دالة عامة <code dir=\"ltr\">csvLine(_ item: Exportable)</code> في كل ملف.","why":"كتابتها في كل ملف ما زالت تكرارًا، ولا تُقرأ كخاصية للنوع."}],"answer":1},{"q":"<p>يخزّن <code dir=\"ltr\">final class ReminderScheduler</code> الخاصية <code dir=\"ltr\">var onFire: (() -&gt; Void)?</code> ويضبط <code dir=\"ltr\">onFire = { self.notify() }</code>. تُظهر أدوات الذاكرة أن الـ schedulers لا تُحرَّر أبدًا. ما الإصلاح؟</p>","options":[{"text":"غيّر <code dir=\"ltr\">onFire</code> من <code dir=\"ltr\">var</code> إلى <code dir=\"ltr\">let</code>.","why":"قابلية التغيير ليست المشكلة؛ فالـ closure المعرّف بـ <code dir=\"ltr\">let</code> سيلتقط <code dir=\"ltr\">self</code> بالقوة نفسها."},{"text":"اضبط <code dir=\"ltr\">onFire = nil</code> داخل <code dir=\"ltr\">deinit</code>.","why":"<code dir=\"ltr\">deinit</code> لا يُنفَّذ أبدًا ما دامت الدورة موجودة، فلن يُنفَّذ ذلك السطر أبدًا."},{"text":"اجعل <code dir=\"ltr\">ReminderScheduler</code> صنفًا فرعيًا من <code dir=\"ltr\">NSObject</code>.","why":"الـ classes في Objective-C تستخدم عدّ المراجع أيضًا؛ وتبقى الدورة."},{"text":"التقط التقاطًا ضعيفًا: <code dir=\"ltr\">onFire = { [weak self] in self?.notify() }</code>.","why":"صحيح. لم يعد الـ closure يمتلك الـ scheduler، فيمكن أن يصل عدّاد المراجع إلى الصفر."}],"answer":3},{"q":"<p>تقرأ <code dir=\"ltr\">load()</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> load</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></code></pre></div>\n","options":[{"text":"قائمة فارغة، دون أي إشارة إلى أن تحميل بياناته فشل.","why":"صحيح. تخلّصت <code dir=\"ltr\">try?</code> من الخطأ وملأت <code dir=\"ltr\">?? []</code> قيمة مكانه. التقط الخطأ وأخبر المستخدم."},{"text":"تنبيهًا يصف التلف.","why":"لا شيء هنا يعرض تنبيهًا؛ فقد تخلّصت <code dir=\"ltr\">try?</code> من الخطأ."},{"text":"عطلًا مع وصف الخطأ.","why":"هذا ما تفعله <code dir=\"ltr\">try!</code>. أما <code dir=\"ltr\">try?</code> فلا تُسقط التطبيق أبدًا؛ بل تنتج <code dir=\"ltr\">nil</code>."},{"text":"المصاريف التي حُمّلت قبل الجزء التالف.","why":"<code dir=\"ltr\">load()</code> إما أن تعيد المصفوفة كاملة أو ترمي خطأً؛ لا توجد نتيجة جزئية."}],"answer":0}],"s-sw-3":[{"q":"<p>يعلن view ابن اسمه <code dir=\"ltr\">AmountField</code> عن <code dir=\"ltr\">@State var amount: Decimal</code>، وينشئه الأب بـ <code dir=\"ltr\">AmountField(amount: budget.limit)</code>. الكتابة في الحقل لا تغيّر حدّ الأب أبدًا. لماذا؟</p>","options":[{"text":"<code dir=\"ltr\">TextField</code> لا يستطيع تعديل قيم <code dir=\"ltr\">Decimal</code>.","why":"مع <code dir=\"ltr\">format:</code> يعدّل <code dir=\"ltr\">TextField</code> قيم <code dir=\"ltr\">Decimal</code> بلا مشكلة؛ النوع ليس المشكلة."},{"text":"يجب أن يستدعي الأب <code dir=\"ltr\">objectWillChange.send()</code>.","why":"هذا ينتمي إلى نظام <code dir=\"ltr\">ObservableObject</code> الأقدم، ولن يربط القيمتين على أي حال."},{"text":"لا يحدّث SwiftUI الآباء إلا حين يختفي الابن.","why":"يحدّث SwiftUI الشاشة بمجرّد تغيّر مصدر الحقيقة؛ لا توجد قاعدة تأخير كهذه."},{"text":"أعطى <code dir=\"ltr\">@State</code> الابن نسخته الخاصة؛ يجب أن يكون <code dir=\"ltr\">@Binding</code>، ويمرّر الأب binding.","why":"صحيح. يجب أن يستعير الابن مصدر الحقيقة الخاص بالأب، لا أن يبدأ مصدرًا جديدًا."}],"answer":3},{"q":"<p>أيّ كود يعرض مجموع الإنفاق بالطريقة التي يتوقّع المستخدمون في بلدان مختلفة قراءة المال بها؟</p>","options":[{"text":"<code dir=\"ltr\">Text(total, format: .currency(code: &quot;USD&quot;))</code>","why":"صحيح. نمط التنسيق يطبّق الإعدادات المحلية للمستخدم في موضع الرمز والفواصل والمنازل العشرية."},{"text":"<code dir=\"ltr\">Text(&quot;$\\(total)&quot;)</code>","why":"هذا يثبّت الرمز ويتجاهل فواصل المستخدم، فتبدو “$1234.5” خاطئة في معظم الأماكن."},{"text":"<code dir=\"ltr\">Text(String(describing: total))</code>","why":"هذا تمثيل مخصّص للتصحيح، بلا عملة ولا قواعد محلية."},{"text":"<code dir=\"ltr\">Text(&quot;\\(total) USD&quot;)</code>","why":"أفضل من لا شيء، لكنه ما زال غير منسّق: لا تجميع للخانات، ولا منازل عشرية ثابتة، ولا إعدادات محلية."}],"answer":0},{"q":"<p>تستخدم القائمة <code dir=\"ltr\">NavigationLink(value: expense)</code> و<code dir=\"ltr\">.navigationDestination(for: Expense.self)</code>. بعد حفظ مصروف جديد، تريد فتح شاشة تفاصيله من الكود. ماذا تضيف؟</p>","options":[{"text":"‏<code dir=\"ltr\">NavigationLink</code> مخفيًا مع <code dir=\"ltr\">isActive:</code> تبدّل قيمته.","why":"هذا نمط قديم مهجور؛ استبدله <code dir=\"ltr\">NavigationStack</code> بالمسارات."},{"text":"تستدعي <code dir=\"ltr\">navigationDestination</code> مرة أخرى مع المصروف الجديد.","why":"الـ modifier الخاص بالوجهة يُعلَن مرة واحدة؛ ولا يطلق التنقّل بنفسه."},{"text":"‏<code dir=\"ltr\">@State private var path: [Expense] = []</code>، و<code dir=\"ltr\">NavigationStack(path: $path)</code>، ثم <code dir=\"ltr\">path.append(saved)</code>.","why":"صحيح. المسار state، فإضافة قيمة إليه تدفع شاشة وجهتها."},{"text":"تعرض شاشة التفاصيل عبر <code dir=\"ltr\">.sheet</code> بدلًا من ذلك.","why":"هذا يحوّل التفاعل إلى عرض منبثق؛ ولا يدفع الشاشة إلى مكدّس التنقّل."}],"answer":2},{"q":"<p>وُضع <code dir=\"ltr\">BudgetStore</code> المعلّم بـ <code dir=\"ltr\">@Observable</code> في الـ environment على مستوى التطبيق. ويحتاج view متداخل بعمق اسمه <code dir=\"ltr\">LimitSettings</code> إلى <code dir=\"ltr\">TextField</code> مربوط بـ <code dir=\"ltr\">store.monthlyLimit</code>. ماذا يكتب هذا الـ view؟</p>","options":[{"text":"<code dir=\"ltr\">@EnvironmentObject var store: BudgetStore</code>","why":"<code dir=\"ltr\">@EnvironmentObject</code> ينتمي إلى <code dir=\"ltr\">ObservableObject</code>؛ ولا يجد الكائنات المضافة عبر <code dir=\"ltr\">.environment(_:)</code>."},{"text":"<code dir=\"ltr\">@State private var store = BudgetStore()</code>","why":"هذا ينشئ مخزنًا جديدًا كليًا يملكه هذا الـ view، منفصلًا عن بيانات التطبيق."},{"text":"<code dir=\"ltr\">@Binding var store: BudgetStore</code>","why":"الـ bindings يمرّرها الأب صراحةً؛ ولا تأتي من الـ environment."},{"text":"<code dir=\"ltr\">@Environment(BudgetStore.self) private var store</code>، ثم <code dir=\"ltr\">@Bindable var store = store</code> داخل <code dir=\"ltr\">body</code>.","why":"صحيح. يوفّر الـ environment المخزن المشترك، وينتج <code dir=\"ltr\">@Bindable</code> القيمة <code dir=\"ltr\">$store.monthlyLimit</code>."}],"answer":3},{"q":"<p>يُعرض <code dir=\"ltr\">AddExpenseView</code> كـ sheet ويجب أن يُغلق نفسه حين ينقر المستخدم Save. ما الطريقة القياسية؟</p>","options":[{"text":"اقرأ <code dir=\"ltr\">@Environment(\\.dismiss) private var dismiss</code> واستدعِ <code dir=\"ltr\">dismiss()</code> بعد الحفظ.","why":"صحيح. <code dir=\"ltr\">dismiss</code> يغلق أيّ عرض يُظهر هذا الـ view، سواء كان sheet أو شاشة دُفعت إلى مكدّس التنقّل."},{"text":"استدعِ <code dir=\"ltr\">NavigationStack.pop()</code>.","why":"لا يوجد API كهذا، والـ sheet ليست جزءًا من مكدّس التنقّل الخاص بالقائمة."},{"text":"اضبط خاصية <code dir=\"ltr\">isPresented</code> الخاصة بالـ view نفسه على <code dir=\"ltr\">false</code>.","why":"علامة الـ sheet ملك للأب؛ والابن لا يملك خاصية كهذه إلا إذا أُعطي binding."},{"text":"احذف الـ view من أبيه عبر <code dir=\"ltr\">removeFromSuperview()</code>.","why":"هذا من UIKit؛ أما الـ views في SwiftUI فأوصاف، لا كائنات يمكنك حذفها."}],"answer":0}],"s-sw-4":[{"q":"<p>كل عملية جلب تستغرق نحو ثانية. كيف تجعل هذا يستغرق نحو ثانية واحدة في المجمل؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"5\"><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\"> loadAll</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, Decimal) {</span></span>\n<span class=\"line\"><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:#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\">: </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\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> gbp </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\"> 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:#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","options":[{"text":"غلّف كل استدعاء في <code dir=\"ltr\">Task { }</code> وأعِد فورًا.","why":"ستعود الدالة قبل وجود النتائج، والمهام غير المهيكلة تفلت من الإلغاء."},{"text":"علّم الدالة بـ <code dir=\"ltr\">@MainActor</code>.","why":"تغيير الـ actor لا يجعل عمليتَي انتظار متتاليتين تتداخلان."},{"text":"استدعِ <code dir=\"ltr\">fetchRate</code> من كتلة <code dir=\"ltr\">DispatchQueue.main.async</code>.","why":"هذا يخلط كود GCD القديم مع async/await، وما زال لا يشغّل الطلبين بالتوازي."},{"text":"استخدم <code dir=\"ltr\">async let eur = …</code> و<code dir=\"ltr\">async let gbp = …</code>، ثم <code dir=\"ltr\">try await (eur, gbp)</code>.","why":"صحيح. تبدأ المهمتان الابنتان فورًا وتعملان في الوقت نفسه."}],"answer":3},{"q":"<p>دالة مساعدة nonisolated تُسند قيمة إلى <code dir=\"ltr\">model.state</code> في نموذج معلّم بـ <code dir=\"ltr\">@MainActor</code>، فتُبلغ Swift 6 بالرسالة “main actor-isolated property ‘state’ can not be mutated from a nonisolated context”. ما الإصلاح الصحيح؟</p>","options":[{"text":"علّم الدالة المساعدة بـ <code dir=\"ltr\">@MainActor</code>، فتعمل حيث تعيش الـ state الخاصة بالنموذج.","why":"صحيح. الآن يستطيع الـ compiler أن يثبت أن كود الـ main actor وحده يلمس الـ state."},{"text":"علّم النموذج بـ <code dir=\"ltr\">@unchecked Sendable</code>.","why":"هذا يُسكت الفحوصات دون أن يجعل أي شيء آمنًا؛ والسباق ما زال موجودًا."},{"text":"أعد الهدف إلى وضع اللغة Swift 5.","why":"هذا يُخفي التشخيص بدل إصلاح سباق البيانات الذي يصفه."},{"text":"انسخ النموذج إلى متغيّر محلي قبل الإسناد.","why":"النموذج class، فالنسخة هي الكائن نفسه والسباق نفسه."}],"answer":0},{"q":"<p>يعيد API الأسعار الحالة HTTP 404 مع صفحة HTML لعملة غير معروفة. وكودك يفكّ ترميز <code dir=\"ltr\">data</code> مباشرة بعد <code dir=\"ltr\">URLSession.shared.data(from:)</code>. ماذا يرى المستخدمون؟</p>","options":[{"text":"‏<code dir=\"ltr\">URLError</code> يقول إن الصفحة غير موجودة.","why":"لا يرمي URLSession أخطاءً لرموز حالة HTTP؛ فالـ 404 استجابة ناجحة على مستوى الشبكة."},{"text":"خطأ فكّ ترميز، مثل “data couldn’t be read because it isn’t in the correct format”.","why":"صحيح. يصل الـ HTML إلى <code dir=\"ltr\">JSONDecoder</code>. افحص رمز الحالة أولًا وارمِ خطأً أوضح."},{"text":"قائمة أسعار فارغة دون أي خطأ.","why":"فكّ ترميز HTML على أنه <code dir=\"ltr\">RatesResponse</code> يفشل بصخب؛ ولا ينتج قيمة فارغة."},{"text":"صفحة الـ HTML معروضة داخل التطبيق.","why":"لا شيء في الكود يعرض جسم الاستجابة."}],"answer":1},{"q":"<p>تربط شاشة تعديل الحقول مباشرة بنموذج SwiftData عبر <code dir=\"ltr\">@Bindable var expense: Expense</code>. ويريد فريق المنتج زر Cancel يتجاهل التغييرات. ما أنظف تصميم؟</p>","options":[{"text":"أبقِ الربط المباشر، واستدعِ <code dir=\"ltr\">context.insert(expense)</code> عند Cancel.","why":"إضافة نموذج موجود لا تتراجع عن التعديلات؛ فالتغييرات صارت على الكائن أصلًا."},{"text":"أبقِ الربط المباشر؛ فالتعديلات لا تُحفظ إلا حين تُغلق الشاشة.","why":"التعديلات تغيّر النموذج فورًا، والـ context الرئيسي يحفظها تلقائيًا."},{"text":"حوّل <code dir=\"ltr\">Expense</code> إلى struct فتكون التعديلات نسخًا.","why":"<code dir=\"ltr\">@Model</code> يتطلّب class؛ لا يمكنك جعل نموذج SwiftData من نوع struct."},{"text":"عدّل نسخ مسوّدة من الحقول في <code dir=\"ltr\">@State</code>، ولا تكتبها في النموذج إلا عند Save.","why":"صحيح. إنه نمط المسوّدة نفسه الذي في نموذج الإضافة: Cancel يرمي المسوّدات."}],"answer":3},{"q":"<p>أيّ اختبار يتحقّق من أن <code dir=\"ltr\">record</code> ترمي <code dir=\"ltr\">BudgetError.overLimit(by: 7)</code>؟</p>","options":[{"text":"<code dir=\"ltr\">#expect(throws: BudgetError.overLimit(by: 7)) { try food.record(title: &quot;Dinner&quot;, amount: 45) }</code>","why":"صحيح. لا ينجح إلا إن رُمي ذلك الخطأ بالضبط، وهذا يعمل لأن <code dir=\"ltr\">BudgetError</code> من نوع <code dir=\"ltr\">Equatable</code>."},{"text":"<code dir=\"ltr\">#expect(try food.record(title: &quot;Dinner&quot;, amount: 45) == .overLimit(by: 7))</code>","why":"<code dir=\"ltr\">record</code> لا تعيد شيئًا؛ الأخطاء تُرمى ولا تُعاد، فلا يوجد ما تقارنه."},{"text":"<code dir=\"ltr\">do { try food.record(title: &quot;Dinner&quot;, amount: 45) } catch { }</code>","why":"دون أي تحقّق، ينجح هذا سواء رُمي شيء أم لا."},{"text":"<code dir=\"ltr\">try #require(food.spent == 57)</code>","why":"هذا يفحص الشيء الخطأ؛ فبعد فشل صحيح يجب أن تبقى <code dir=\"ltr\">spent</code> دون تغيير."}],"answer":0}]},"final":[{"q":"<p><code dir=\"ltr\">expenses</code> مصفوفة فارغة. ما قيمة <code dir=\"ltr\">label</code>؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"6\"><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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> label </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.title.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">uppercased</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"NONE\"</span></span></code></pre></div>\n","options":[{"text":"يتعطّل التطبيق، لأن المصفوفة فارغة.","why":"<code dir=\"ltr\">first</code> تعيد optional بدل أن تُسقط التطبيق، و<code dir=\"ltr\">?.</code> تتوقّف بأمان عند <code dir=\"ltr\">nil</code>."},{"text":"<code dir=\"ltr\">nil</code>","why":"سلسلة الـ optional تنتج <code dir=\"ltr\">nil</code>، لكن <code dir=\"ltr\">?? &quot;NONE&quot;</code> تستبدلها بالقيمة الافتراضية."},{"text":"نص فارغ.","why":"لا يوجد عنوان، فلا شيء يُحوَّل إلى أحرف كبيرة؛ وتُستخدم القيمة الافتراضية بدلًا من ذلك."},{"text":"<code dir=\"ltr\">&quot;NONE&quot;</code>","why":"صحيح. <code dir=\"ltr\">first</code> تساوي <code dir=\"ltr\">nil</code>، فتتوقّف السلسلة مبكرًا، وتقدّم <code dir=\"ltr\">??</code> القيمة الافتراضية."}],"answer":3},{"q":"<p>يحتاج Pocket Budget إلى إعداد لترتيب الفرز فيه ثلاثة خيارات بالضبط (الأحدث، الأقدم، الأكبر)، يُعرض في منتقٍ ويُحفظ على القرص. أيّ نوع هو الأنسب؟</p>","options":[{"text":"<code dir=\"ltr\">enum SortOrder: String, CaseIterable { case newest, oldest, largest }</code>","why":"صحيح. لا توجد إلا القيم الصالحة، و<code dir=\"ltr\">allCases</code> تغذّي المنتقي، والقيمة الخام تُحفظ بسهولة."},{"text":"<code dir=\"ltr\">let sortOrder: String</code>","why":"أي نص يُترجَم، بما في ذلك الأخطاء الإملائية، والمنتقي لا يملك قائمة خيارات."},{"text":"<code dir=\"ltr\">struct SortOrder { var isNewest: Bool; var isLargest: Bool }</code>","why":"القيم المنطقية تسمح بتركيبات مستحيلة، مثل أن تكون العلامتان صحيحتين معًا."},{"text":"‏<code dir=\"ltr\">class SortOrder</code> مع ثلاثة أصناف فرعية.","why":"الوراثة أثقل بكثير مما تحتاجه مجموعة ثابتة من الخيارات، وأصعب في الحفظ."}],"answer":0},{"q":"<p>لماذا يحتفظ نموذج إضافة المصروف بـ <code dir=\"ltr\">title</code> و<code dir=\"ltr\">amount</code> و<code dir=\"ltr\">category</code> في خصائص <code dir=\"ltr\">@State</code> خاصة به بدل الكتابة في المخزن مع كل ضغطة مفتاح؟</p>","options":[{"text":"لأن المخازن من نوع <code dir=\"ltr\">@Observable</code> لا يمكن تعديلها من النماذج.","why":"النماذج تعدّل الكائنات المراقَبة عبر <code dir=\"ltr\">@Bindable</code> طوال الوقت؛ ليس هذا هو السبب."},{"text":"لأن <code dir=\"ltr\">TextField</code> لا يعمل إلا مع <code dir=\"ltr\">@State</code>.","why":"<code dir=\"ltr\">TextField</code> يقبل أي binding، بما في ذلك binding إلى داخل كائن مراقَب."},{"text":"لأن المخزن لا يقبل إلا قيم <code dir=\"ltr\">Expense</code> مكتملة.","why":"كان يمكن للمخزن أن يقبل تحديثات جزئية؛ السبب الحقيقي يتعلّق بنيّة المستخدم."},{"text":"ليكون النموذج مسوّدة: Cancel يتجاهلها دون أن يمسّ البيانات المحفوظة.","why":"صحيح. المسوّدات من أنواع القيم تجعل الإلغاء مجانيًا وتُبقي البيانات نصف المكتوبة خارج المخزن."}],"answer":3},{"q":"<p>أضفت <code dir=\"ltr\">case gifts</code> إلى <code dir=\"ltr\">Category</code>. في المشروع ثلاث جمل <code dir=\"ltr\">switch</code> على <code dir=\"ltr\">Category</code>، ومع ذلك تنجح الترجمة دون أخطاء. ما التفسير الأرجح؟</p>","options":[{"text":"لا تفحص Swift شمول جمل <code dir=\"ltr\">switch</code> للـ enums ذات القيم الخام.","why":"القيم الخام لا تُحدث فرقًا؛ كل switch على enum تُفحص للتحقّق من شمولها."},{"text":"لا تُفحص حالات الـ enum الجديدة إلا حين تشغّل التطبيق.","why":"الشمول فحص وقت الترجمة، لا وقت التشغيل أبدًا."},{"text":"جمل الـ switch تلك تستخدم <code dir=\"ltr\">default:</code>، التي تبتلع الحالة الجديدة بصمت.","why":"صحيح. لهذا يجب أن تسرد جمل switch على الـ enums الخاصة بك حالاتها صراحةً."},{"text":"لا يفحص Xcode إلا جمل switch في الملفات المفتوحة.","why":"الـ compiler يفحص كل ملف يبنيه، مفتوحًا كان أم لا."}],"answer":2},{"q":"<p>عدة مهام تجلب الأسعار بالتوازي ويجب أن تحدّث كلها cache مشتركًا واحدًا. أيّ تعريف يُبقي الـ cache خاليًا من سباقات البيانات؟</p>","options":[{"text":"<code dir=\"ltr\">actor RateCache { private var rates: [String: Decimal] = [:] }</code> مع methods للقراءة والحفظ.","why":"صحيح. الـ actor ينفّذ استدعاءً واحدًا في كل مرة على الـ state الخاصة به، والمستدعون يجب أن يستخدموا <code dir=\"ltr\">await</code>."},{"text":"<code dir=\"ltr\">final class RateCache { var rates: [String: Decimal] = [:] }</code>","why":"الـ class العادي ذو الـ state القابلة للتغيير ليس <code dir=\"ltr\">Sendable</code>؛ ولن تسمح Swift 6 للمهام بمشاركته."},{"text":"‏<code dir=\"ltr\">struct RateCache { var rates: [String: Decimal] }</code> يُمرَّر إلى كل مهمة.","why":"كل مهمة تحصل على نسختها الخاصة، فلا تصل تحديثاتها أبدًا إلى cache مشترك."},{"text":"متغيّر عام <code dir=\"ltr\">var rates: [String: Decimal]</code>.","why":"الـ state العامة القابلة للتغيير هي بالضبط ما تعتبره Swift 6 غير آمن بين المهام."}],"answer":0},{"q":"<p>يحتوي الـ JSON على <code dir=\"ltr\">&quot;total_spent&quot;: 12.5</code> وفي الـ struct الخاص بك <code dir=\"ltr\">let totalSpent: Decimal</code>. يرمي فكّ الترميز <code dir=\"ltr\">keyNotFound</code>. ما أنظف إصلاح؟</p>","options":[{"text":"أعد تسمية الخاصية إلى <code dir=\"ltr\">total_spent</code>.","why":"يعمل، لكنه يُدخل أسلوب التسمية الخاص بالخادم إلى كود Swift الخاص بك."},{"text":"غيّر النوع إلى <code dir=\"ltr\">Double</code>.","why":"النوع ليس المشكلة؛ اسم المفتاح هو الذي لا يتطابق."},{"text":"اجعل الخاصية optional.","why":"يختفي الخطأ، لكن القيمة تصبح <code dir=\"ltr\">nil</code> دائمًا لأن المفتاح ما زال لا يتطابق."},{"text":"اضبط <code dir=\"ltr\">decoder.keyDecodingStrategy = .convertFromSnakeCase</code>.","why":"صحيح. كل مفتاح بصيغة snake_case يُربط تلقائيًا بالخاصية المقابلة بصيغة camelCase."}],"answer":3},{"q":"<p>يريد المستخدمون السحب للأسفل على قائمة الأسعار لتحديثها. أيّ modifier يضيف ذلك؟</p>","options":[{"text":"<code dir=\"ltr\">.task { await model.load() }</code>","why":"<code dir=\"ltr\">.task</code> تعمل حين يظهر الـ view؛ ولا تضيف إيماءة سحب."},{"text":"<code dir=\"ltr\">.refreshable { await model.load() }</code>","why":"صحيح. يضيف السحب للتحديث ويعرض مؤشّر التحميل حتى تعود الدالة غير المتزامنة."},{"text":"<code dir=\"ltr\">.onTapGesture { Task { await model.load() } }</code>","why":"هذا يحدّث عند النقر لا عند السحب، ولا يعرض مؤشّر التحديث الخاص بالنظام."},{"text":"<code dir=\"ltr\">.sheet(isPresented:)</code>","why":"الـ sheet تعرض شاشة أخرى؛ ولا علاقة لها بالتحديث."}],"answer":1},{"q":"<p>في أيّ موقف تكون <code dir=\"ltr\">try?</code> خيارًا جيدًا؟</p>","options":[{"text":"حذف ملف تصدير مؤقت قد يكون اختفى أصلًا، حيث لا يغيّر الفشل شيئًا.","why":"صحيح. سبب الفشل لا يهم فعلًا، فتجاهله أمر صادق."},{"text":"حفظ المصروف الجديد للمستخدم.","why":"إن فشل الحفظ فيجب إخبار المستخدم؛ و<code dir=\"ltr\">try?</code> ستُخفي ذلك."},{"text":"فكّ ترميز استجابة أسعار الصرف.","why":"فشل فكّ الترميز يجب أن يصبح حالة خطأ ظاهرة، لا <code dir=\"ltr\">nil</code> صامتة."},{"text":"تسجيل مصروف مقابل حدّ الميزانية.","why":"الخطأ المحدّد، مثل تجاوز الحدّ، هو ما يحدّد الرسالة التي يراها المستخدم."}],"answer":0},{"q":"<p>أيّ تعريف يعرض المصاريف المحفوظة الأحدث أولًا ويتحدّث من تلقاء نفسه حين تضيف شاشة أخرى مصروفًا؟</p>","options":[{"text":"<code dir=\"ltr\">@State private var expenses: [Expense] = []</code>","why":"هذه مصفوفة في الذاكرة سيكون عليك ملؤها وتحديثها يدويًا."},{"text":"<code dir=\"ltr\">let expenses = try? context.fetch(FetchDescriptor&lt;Expense&gt;())</code>","why":"الجلب لمرة واحدة لا يتحدّث حين تتغيّر البيانات لاحقًا."},{"text":"<code dir=\"ltr\">@Environment(\\.modelContext) private var expenses</code>","why":"هذا هو الـ context المستخدم للكتابة، لا قائمة مصاريف."},{"text":"<code dir=\"ltr\">@Query(sort: \\Expense.date, order: .reverse) private var expenses: [Expense]</code>","why":"صحيح. <code dir=\"ltr\">@Query</code> يجلب ويرتّب ويُبقي النتيجة محدّثة كلما تغيّر المخزن."}],"answer":3},{"q":"<p>ماذا يَعِد <code dir=\"ltr\">some View</code> في <code dir=\"ltr\">var body: some View</code>؟</p>","options":[{"text":"أن <code dir=\"ltr\">body</code> يمكن أن يعيد نوع view مختلفًا في كل مرة يُستدعى فيها.","why":"هذا ما تسمح به <code dir=\"ltr\">any View</code>؛ أما <code dir=\"ltr\">some</code> فتثبّت نوعًا واحدًا."},{"text":"أن الـ view هو class.","why":"<code dir=\"ltr\">some</code> لا تقول شيئًا عن الـ classes؛ والـ views في SwiftUI عادةً structs."},{"text":"أن <code dir=\"ltr\">body</code> يعيد نوعًا واحدًا محدّدًا يتوافق مع <code dir=\"ltr\">View</code>، يعرفه الـ compiler ولا تحتاج أنت إلى كتابته.","why":"صحيح. إنه نوع مُعتِم (opaque type) يُخفي اسم generic طويلًا."},{"text":"أن الـ view سيُرسم على thread في الخلفية.","why":"<code dir=\"ltr\">some</code> تتعلّق بالأنواع لا بالـ threads؛ والـ views تُرسم على الـ main actor."}],"answer":2},{"q":"<p>يفشل رفع نسخة جديدة إلى App Store Connect لأن رقم الـ Build ذاك استُخدم من قبل لهذا الإصدار. ماذا تفعل؟</p>","options":[{"text":"زِد رقم الـ Build في إعدادات الهدف، وأرشِف مرة أخرى، ثم ارفع.","why":"صحيح. كل رفع يحتاج إلى رقم Build جديد؛ ويمكن أن يبقى الـ Version كما هو."},{"text":"احذف النسخة السابقة من TestFlight وأعد المحاولة.","why":"أرقام الـ Build تبقى مستخدمة حتى بعد إنهاء صلاحية النسخة."},{"text":"غيّر الـ bundle identifier.","why":"هذا ينشئ تطبيقًا مختلفًا كليًا، فتخسر سجلّك في App Store Connect."},{"text":"انتظر 90 يومًا حتى تنتهي صلاحية النسخة القديمة.","why":"انتهاء الصلاحية يُنهي الاختبار؛ ولا يحرّر رقم الـ Build."}],"answer":0},{"q":"<p>هل يُترجَم هذا الكود؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"7\"><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\">@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\"> Settings</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\"> limit: Decimal </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> settings </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Settings</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">settings.limit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 200</span></span></code></pre></div>\n","options":[{"text":"لا، لأن <code dir=\"ltr\">settings</code> ثابت معرّف بـ <code dir=\"ltr\">let</code>.","why":"في الـ class، <code dir=\"ltr\">let</code> تثبّت الكائن الذي يشير إليه الاسم، لا خصائص الكائن."},{"text":"لا، لأن خصائص <code dir=\"ltr\">@Observable</code> للقراءة فقط.","why":"الخصائص المراقَبة متغيّرات <code dir=\"ltr\">var</code> عادية؛ والماكرو يضيف التتبّع فقط."},{"text":"نعم، لكن التغيير يضيع لأن <code dir=\"ltr\">settings</code> نُسخ.","why":"الـ classes لا تُنسخ عند الإسناد؛ هناك كائن واحد وقد تغيّر."},{"text":"نعم؛ المرجع ثابت، لكن خاصية <code dir=\"ltr\">var</code> في الكائن ما زالت قابلة للتغيير.","why":"صحيح. هذا هو سلوك المرجع: <code dir=\"ltr\">let</code> تجمّد المرجع فقط."}],"answer":3}],"glossary":[{"id":"type-inference","term":"استنتاج النوع (Type inference)","definition":"قدرة Swift على معرفة نوع القيمة من القيمة التي تسندها، فيكون <code dir=\"ltr\">let cups = 3</code> من النوع <code dir=\"ltr\">Int</code> دون تحديد صريح. ويبقى النوع ثابتًا بعد ذلك.","lesson":"l-sw-1-1"},{"id":"decimal","term":"Decimal","definition":"نوع أرقام من Foundation يخزّن الأرقام بالنظام العشري بدقة، ما يجعله الخيار الصحيح للمال. وعلى عكس <code dir=\"ltr\">Double</code>، إضافة 0.1 عشر مرات تعطي 1 بالضبط.","lesson":"l-sw-1-1"},{"id":"optional","term":"Optional","definition":"نوع مثل <code dir=\"ltr\">Decimal?</code> يحمل إما قيمة أو <code dir=\"ltr\">nil</code>. لا تسمح لك Swift باستخدامه كقيمة عادية حتى تفكّه بأمان.","lesson":"l-sw-1-2"},{"id":"guard-let","term":"guard let","definition":"جملة تفكّ optional أو تفحص شرطًا، ويجب أن يغادر فرع <code dir=\"ltr\">else</code> فيها النطاق. وبعدها تبقى القيمة المفكوكة متاحة حتى نهاية الدالة.","lesson":"l-sw-1-2"},{"id":"nil-coalescing","term":"عامل الدمج مع nil (??)","definition":"العامل <code dir=\"ltr\">??</code> الذي يعيد القيمة الموجودة داخل optional، أو قيمة افتراضية حين يكون الـ optional مساويًا لـ <code dir=\"ltr\">nil</code>، كما في <code dir=\"ltr\">note ?? &quot;No note&quot;</code>.","lesson":"l-sw-1-2"},{"id":"force-unwrap","term":"الفكّ القسري (Force unwrap !)","definition":"كتابة <code dir=\"ltr\">!</code> بعد optional لأخذ قيمته دون فحص. إن كان الـ optional مساويًا لـ <code dir=\"ltr\">nil</code> يتعطّل التطبيق فورًا، فاستخدمه فقط حين تكون <code dir=\"ltr\">nil</code> خطأً من المبرمج.","lesson":"l-sw-1-2"},{"id":"exhaustive-switch","term":"الـ switch الشاملة (Exhaustive switch)","definition":"قاعدة Swift التي تُلزم جملة <code dir=\"ltr\">switch</code> بمعالجة كل قيمة ممكنة. ومع الـ enums تجعل الـ compiler يسرد كل switch يجب تحديثها حين تضيف حالة.","lesson":"l-sw-1-3"},{"id":"closure","term":"Closure","definition":"كتلة كود بلا اسم يمكن تخزينها، وتمريرها إلى دوال مثل <code dir=\"ltr\">filter</code> أو <code dir=\"ltr\">map</code>، واستدعاؤها لاحقًا. وتلتقط المتغيّرات التي تستخدمها من النطاق المحيط بها.","lesson":"l-sw-1-4"},{"id":"value-semantics","term":"سلوك القيمة (Value semantics)","definition":"سلوك الـ structs والـ enums: إسناد القيمة أو تمريرها ينتج نسخة مستقلّة، فتغيير النسخة لا يؤثّر أبدًا في الأصل.","lesson":"l-sw-2-1"},{"id":"reference-semantics","term":"سلوك المرجع (Reference semantics)","definition":"سلوك الـ classes والـ actors: إسناد instance ينسخ مرجعًا، فيمكن لعدة أسماء أن تشير إلى الكائن نفسه وترى تغييرات بعضها.","lesson":"l-sw-2-1"},{"id":"mutating","term":"mutating","definition":"كلمة مفتاحية توضع على methods الـ structs والـ enums التي تغيّر خصائص القيمة نفسها. ولا تُستدعى الـ mutating methods إلا على <code dir=\"ltr\">var</code>، لا على <code dir=\"ltr\">let</code> أبدًا.","lesson":"l-sw-2-1"},{"id":"retain-cycle","term":"دورة الاحتفاظ (Retain cycle)","definition":"كائنان، أو كائن وclosure، يحمل كلٌّ منهما مرجعًا قويًا إلى الآخر، فلا يستطيع ARC تحرير أيٍّ منهما. والتقاط <code dir=\"ltr\">[weak self]</code> داخل الـ closure يكسر الدورة.","lesson":"l-sw-2-1"},{"id":"associated-value","term":"القيمة المرتبطة (Associated value)","definition":"بيانات مرفقة بحالة معيّنة من enum لكل instance، مثل <code dir=\"ltr\">case card(lastFour: String)</code>. وتقرؤها من جديد عبر مطابقة الأنماط مثل <code dir=\"ltr\">case .card(let lastFour)</code>.","lesson":"l-sw-2-2"},{"id":"raw-value","term":"القيمة الخام (Raw value)","definition":"نص <code dir=\"ltr\">String</code> أو رقم ثابت يُخزَّن لكل حالة في enum معرّف مثل <code dir=\"ltr\">enum Category: String</code>. ويحوّل <code dir=\"ltr\">init(rawValue:)</code> في الاتجاه المعاكس ويعيد <code dir=\"ltr\">nil</code> للقيم غير المعروفة.","lesson":"l-sw-2-2"},{"id":"protocol","term":"Protocol","definition":"مجموعة مسمّاة من المتطلّبات، مثل الخصائص والـ methods، تستطيع الـ structs والـ classes والـ enums تبنّيها. والكود المكتوب للـ protocol يعمل مع كل نوع متوافق معه.","lesson":"l-sw-2-3"},{"id":"protocol-extension","term":"Protocol extension","definition":"extension على protocol يوفّر تنفيذات افتراضية، فيكتسب كل نوع متوافق ذلك السلوك دون كتابته مرة أخرى.","lesson":"l-sw-2-3"},{"id":"typed-throws","term":"Typed throws","definition":"صيغة في Swift 6 مثل <code dir=\"ltr\">throws(BudgetError)</code> تسمّي نوع الخطأ الدقيق الذي قد ترميه الدالة، فيستطيع المستدعون كتابة switch شاملة على الخطأ دون حالة التقاط عامة.","lesson":"l-sw-2-4"},{"id":"modifier","term":"View modifier","definition":"method مثل <code dir=\"ltr\">.padding()</code> أو <code dir=\"ltr\">.font(.headline)</code> تغلّف view في SwiftUI داخل view جديد مع تطبيق تغيير. وتُطبَّق الـ modifiers بالترتيب، لذلك ترتيبها مهم.","lesson":"l-sw-3-1"},{"id":"state","term":"@State","definition":"property wrapper في SwiftUI يخزّن قيمة يملكها الـ view خارج الـ struct الخاص به، فتبقى بعد كل re-render وتطلق تحديثًا حين تتغيّر.","lesson":"l-sw-3-2"},{"id":"binding","term":"Binding","definition":"اتصال في الاتجاهين مع قيمة يملكها طرف آخر، يُكتب بالبادئة <code dir=\"ltr\">$</code>. وعناصر التحكّم مثل <code dir=\"ltr\">Toggle</code> و<code dir=\"ltr\">TextField</code> تأخذ bindings لتقرأ مصدر الحقيقة وتكتب فيه.","lesson":"l-sw-3-2"},{"id":"observable","term":"@Observable","definition":"ماكرو من إطار Observation يجعل خصائص الـ class قابلة للتتبّع، فلا يعيد SwiftUI رسم إلا الـ views التي قرأ الـ body الخاص بها خاصية تغيّرت.","lesson":"l-sw-3-2"},{"id":"navigation-stack","term":"NavigationStack","definition":"حاوية في SwiftUI تدفع الشاشات وتُخرجها. يختار <code dir=\"ltr\">NavigationLink(value:)</code> قيمة، ويقرّر <code dir=\"ltr\">.navigationDestination(for:)</code> أيّ شاشة تعرضها.","lesson":"l-sw-3-3"},{"id":"structured-concurrency","term":"الـ concurrency المهيكلة (Structured concurrency)","definition":"تشغيل العمل غير المتزامن كمهام أبناء، عبر <code dir=\"ltr\">async let</code> أو مجموعات المهام، يجب أن تنتهي قبل أن يعود الأب. وإلغاء الأب يلغي كل الأبناء تلقائيًا.","lesson":"l-sw-4-1"},{"id":"actor","term":"Actor","definition":"نوع مرجعي يحمي الـ state القابلة للتغيير الخاصة به بتنفيذ استدعاء واحد في كل مرة. والكود خارج الـ actor يجب أن يستخدم <code dir=\"ltr\">await</code> للوصول إليه، وهذا يمنع سباقات البيانات.","lesson":"l-sw-4-1"},{"id":"sendable","term":"Sendable","definition":"protocol يعلّم الأنواع الآمنة للتمرير بين نطاقات الـ concurrency. والـ structs المبنية من قيم Sendable تستوفيه تلقائيًا؛ أما الـ classes العادية ذات الخصائص القابلة للتغيير فلا.","lesson":"l-sw-4-1"},{"id":"main-actor","term":"@MainActor","definition":"سمة تعزل الكود أو النوع على الـ main actor، أي الـ main thread الذي تعمل عليه الواجهة. ومشاريع تطبيقات Xcode الجديدة تجعله العزل الافتراضي.","lesson":"l-sw-4-1"},{"id":"codable","term":"Codable","definition":"الجمع بين <code dir=\"ltr\">Decodable</code> و<code dir=\"ltr\">Encodable</code>، ما يتيح للـ compiler توليد كود يحوّل أنواعك من صيغ مثل JSON وإليها.","lesson":"l-sw-4-2"},{"id":"swiftdata-model","term":"@Model (SwiftData)","definition":"ماكرو يحوّل الـ class إلى بيانات SwiftData محفوظة بشكل دائم. تقرأ النماذج عبر <code dir=\"ltr\">@Query</code>، وتضيفها أو تحذفها عبر الـ model context الذي يحفظ تلقائيًا.","lesson":"l-sw-4-3"},{"id":"swift-testing","term":"Swift Testing","definition":"إطار الاختبار من Apple، حيث الاختبارات دوال معلّمة بـ <code dir=\"ltr\">@Test</code>، تُفحص بـ <code dir=\"ltr\">#expect</code> و<code dir=\"ltr\">#require</code>، وتُشغَّل على مدخلات متعدّدة عبر <code dir=\"ltr\">arguments:</code>.","lesson":"l-sw-4-4"},{"id":"testflight","term":"TestFlight","definition":"خدمة Apple لتوزيع النسخ التجريبية. النسخ المرفوعة إلى App Store Connect تصل فورًا إلى 100 مختبِر داخلي كحدّ أقصى، وإلى 10,000 مختبِر خارجي كحدّ أقصى بعد Beta App Review.","lesson":"l-sw-4-4"}]}