{"id":"l-sw-1-2","courseId":"swift-essentials","lang":"ar","kind":"lesson","summary":"اقرأ أنواع الـ optional، وفكّها بأمان باستخدام if let وguard let و??، واشرح لماذا يُعدّ الفكّ القسري عطلًا ينتظر لحظته.","html":"<p>ينقر المستخدم على Add Expense، ويكتب “12.50” في حقل المبلغ، ثم ينقر Save. يستقبل كودك <code dir=\"ltr\">String</code>. وتحويلها إلى رقم قد يفشل: ربما الحقل فارغ، أو ربما كتب المستخدم “twelve”. كثير من اللغات تعطيك في هذه الحالة <code dir=\"ltr\">NaN</code> أو صفرًا أو استثناءً. أما Swift فتعطيك <strong>optional</strong> (قيمة اختيارية)، وتُلزمك بالتعامل مع حالة الغياب قبل أن يُترجَم الكود.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ما هو الـ optional</h2>\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\">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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> typed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"12.50\"</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\"> Decimal</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\">: typed)   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// amount is Decimal?, not Decimal</span></span></code></pre></div>\n<p>علامة الاستفهام في <code dir=\"ltr\">Decimal?</code> تعني «قيمة <code dir=\"ltr\">Decimal</code>، أو لا شيء». و«لا شيء» تُكتب <code dir=\"ltr\">nil</code>. في الداخل، الـ optional هو enum له حالتان، <code dir=\"ltr\">.some(value)</code> و<code dir=\"ltr\">.none</code>، ولهذا يمكنك أن تتخيّله صندوقًا إما ممتلئًا أو فارغًا.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 220\" role=\"img\" aria-labelledby=\"l-sw-1-2-t1\"><defs><marker id=\"arrow-l-sw-1-2-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-1-2-t1\">تعيد Decimal(string:) قيمة Decimal اختيارية. مع \"12.50\" يحمل الصندوق 12.5؛ ومع نص فارغ يكون الصندوق فارغًا، أي nil. الفكّ يفتح الصندوق.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"80\" width=\"170\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"105\" y=\"113\" text-anchor=\"middle\">Decimal(string:)</text>\n  <path class=\"d-arrow\" d=\"M190 100 L300 50\" marker-end=\"url(#arrow-l-sw-1-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M190 116 L300 170\" marker-end=\"url(#arrow-l-sw-1-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"305\" y=\"20\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"380\" y=\"47\" text-anchor=\"middle\">.some(12.5)</text>\n  <text class=\"d-label-muted\" x=\"380\" y=\"68\" text-anchor=\"middle\">\"12.50\"</text>\n  <rect class=\"d-box-warn\" x=\"305\" y=\"140\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"380\" y=\"167\" text-anchor=\"middle\">nil</text>\n  <text class=\"d-label-muted\" x=\"380\" y=\"188\" text-anchor=\"middle\">\"\" أو \"twelve\"</text>\n  <path class=\"d-arrow\" d=\"M455 50 L540 50\" marker-end=\"url(#arrow-l-sw-1-2-1)\"/>\n  <text class=\"d-label\" x=\"590\" y=\"55\" text-anchor=\"middle\">افتحه</text>\n  <path class=\"d-arrow\" d=\"M455 170 L540 170\" marker-end=\"url(#arrow-l-sw-1-2-1)\"/>\n  <text class=\"d-label\" x=\"590\" y=\"175\" text-anchor=\"middle\">عالِجه</text>\n</svg><figcaption>الـ optional صندوق إما ممتلئ أو فارغ</figcaption></figure>\n<p>الـ optional ليس هو القيمة التي في داخله. جرّب <code dir=\"ltr\">amount + 5</code> وسيوقفك الـ compiler بالرسالة “value of optional type ‘Decimal?’ must be unwrapped to a value of type ‘Decimal’”. هذا الخطأ هو الميزة نفسها. كل مكان قد تغيب فيه القيمة مُعلَّم في النوع، ولن تسمح لك Swift بأن تنساه.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">if let: استخدم القيمة في فرع واحد</h2>\n<p>يفحص <code dir=\"ltr\">if let</code> الصندوق، وحين يكون ممتلئًا يعطيك القيمة باسم جديد غير optional:</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\">if</span><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\"> Decimal</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\">: typed) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Adding </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">amount</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// amount is a Decimal here</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\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"That isn't a number\"</span><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>حين يطابق الاسم الجديد اسم optional موجود، تسمح لك Swift 5.7 وما بعدها بحذف الطرف الأيمن: <code dir=\"ltr\">if let amount { … }</code>. الاسم الجديد <code dir=\"ltr\">amount</code> لا يوجد إلا داخل الأقواس، وهذا مثالي حين تهمّك القيمة في فرع صغير واحد.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">guard let: اخرج مبكرًا وأبقِ المسار السعيد مستويًا</h2>\n<p>في أغلب الأحيان، القيمة الغائبة تعني «توقّف هنا». و<code dir=\"ltr\">guard let</code> يقول ذلك بالضبط. هذه هي الدالة التي يستخدمها Pocket Budget للتحقّق من حقل المبلغ:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">AmountParser.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\">func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> text: </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:#CF222E;--shiki-dark:#FF7B72\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> trimmed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> text.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">trimmingCharacters</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .whitespaces)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    guard</span><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\"> Decimal</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\">: trimmed), amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</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:#CF222E;--shiki-dark:#FF7B72\">        return</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:#CF222E;--shiki-dark:#FF7B72\">    return</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>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"12.50\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Optional(12.5)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)        </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// nil</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"-3\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// nil</span></span></code></pre></div>\n<p>يجب أن تغادر كتلة <code dir=\"ltr\">else</code> النطاق الحالي باستخدام <code dir=\"ltr\">return</code> أو <code dir=\"ltr\">throw</code> أو <code dir=\"ltr\">break</code> أو <code dir=\"ltr\">continue</code>، والـ compiler يفرض ذلك. وفي المقابل، يبقى <code dir=\"ltr\">amount</code> متاحًا كقيمة <code dir=\"ltr\">Decimal</code> عادية حتى نهاية الدالة. الفاصلة تضيف شرطًا ثانيًا، فيرفض guard واحد الحالتين معًا: «ليس رقمًا» و«صفر أو سالب». الكود المكتوب بهذه الطريقة يُقرأ من الأعلى إلى الأسفل: الفحوصات أولًا، ثم العمل الحقيقي، بلا هرم من جمل <code dir=\"ltr\">if</code> المتداخلة.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>اجعل guard خيارك الافتراضي</span></p>\n<p>استخدم <code dir=\"ltr\">guard let</code> في أعلى الدوال، و<code dir=\"ltr\">if let</code> للفروع القصيرة المحلية. إن وجدت نفسك داخل ثلاث جمل <code dir=\"ltr\">if let</code> متداخلة، فأعد كتابتها على شكل guards.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">?? والـ optional chaining</h2>\n<p>أحيانًا يكون للقيمة الغائبة بديل واضح. عامل الدمج مع nil‏ <code dir=\"ltr\">??</code> يقدّمه لك:</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> note: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</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:#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\"> note </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"No note\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        // \"No note\"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> noteLength </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> note</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">count</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    // 0</span></span></code></pre></div>\n<p><code dir=\"ltr\">note?.count</code> هو <strong>optional chaining</strong> (السلسلة الاختيارية): إذا كان <code dir=\"ltr\">note</code> يساوي <code dir=\"ltr\">nil</code>، يصبح التعبير كله <code dir=\"ltr\">nil</code> بدل أن يتعطّل التطبيق، ثم يحوّل <code dir=\"ltr\">??</code> ذلك إلى <code dir=\"ltr\">0</code>. يمكن أن تكون السلاسل طويلة (<code dir=\"ltr\">expense?.category?.name</code>)، وتتوقّف عند أول <code dir=\"ltr\">nil</code>.</p>\n<p>والـ optionals قرار تصميمي أيضًا، لا مجرّد شيء تعطيك إيّاه الدوال. في Pocket Budget، لكل مصروف عنوان ومبلغ دائمًا، فهما <code dir=\"ltr\">String</code> و<code dir=\"ltr\">Decimal</code> عاديان. أما الملاحظة فاختيارية فعلًا، لذلك نوعها <code dir=\"ltr\">String?</code>. قاوم إغراء جعل الأشياء optional «من باب الاحتياط»: كل <code dir=\"ltr\">?</code> تضيفها هي سؤال على كل من يقرأ تلك القيمة أن يجيب عنه. اجعل الخاصية optional فقط حين يكون «لا قيمة» حالة حقيقية لها معنى.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">الفكّ القسري</h2>\n<p>هناك طريقة أخرى لفتح الصندوق: <code dir=\"ltr\">!</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\"> amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Decimal</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\">: typed)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span></span></code></pre></div>\n<p>إذا كانت <code dir=\"ltr\">typed</code> تساوي “12.50”، يعمل هذا. وإذا كان الحقل فارغًا، يموت تطبيقك بالرسالة “Fatal error: Unexpectedly found nil while unwrapping an Optional value”. لا رسالة خطأ للمستخدم، ولا فرصة للتعافي.</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\">Decimal(string: field)!</code> ينجح في كل اختبار تكتبه بأرقام معقولة، ثم يتعطّل عند أول مستخدم ينقر Save وحقل المبلغ فارغ. كل ما يأتي من شخص أو ملف أو من الشبكة يمكن أن يكون <code dir=\"ltr\">nil</code>. استخدم <code dir=\"ltr\">guard let</code> واعرض رسالة بدلًا من ذلك.</p>\n</aside>\n<p>للعامل <code dir=\"ltr\">!</code> استخدامات مشروعة، مثل <code dir=\"ltr\">URL(string: &quot;https://example.com&quot;)!</code> المبني من قيمة كتبتها بنفسك. هنا، <code dir=\"ltr\">nil</code> تعني خطأً إملائيًا في كودك، والتعطّل أثناء التطوير هو أسرع طريقة لاكتشافه. كن مستعدًا للدفاع عن كل <code dir=\"ltr\">!</code> أثناء مراجعة الكود.</p>\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>تقرأ <code dir=\"ltr\">Decimal(string:)</code> أكبر عدد ممكن من الأرقام في البداية: <code dir=\"ltr\">Decimal(string: &quot;12,50&quot;)</code> يعطي <code dir=\"ltr\">12</code> لا <code dir=\"ltr\">nil</code>. في درس <a href=\"/ar/lesson/swift-essentials/l-sw-3-3\">القوائم والتنقّل والنماذج</a> تستبدل التحليل اليدوي بحقل <code dir=\"ltr\">TextField</code> يفهم الإعدادات المحلية للمستخدم.</p>\n</aside>\n<p>مع الـ optionals صار بإمكانك التعامل مع قيمة واحدة غائبة بأمان. بعد ذلك ستخزّن قيمًا كثيرة دفعة واحدة في المصفوفات والقواميس والمجموعات، وتتنقّل بينها بحلقات التكرار و<code dir=\"ltr\">switch</code>.</p>\n","toc":[{"id":"sec-1","text":"ما هو الـ optional","level":2},{"id":"sec-2","text":"if let: استخدم القيمة في فرع واحد","level":2},{"id":"sec-3","text":"guard let: اخرج مبكرًا وأبقِ المسار السعيد مستويًا","level":2},{"id":"sec-4","text":"?? والـ optional chaining","level":2},{"id":"sec-5","text":"الفكّ القسري","level":2}],"takeaways":["الـ optional مثل <code dir=\"ltr\">Decimal?</code> إما أن يحمل قيمة أو يحمل <code dir=\"ltr\">nil</code>، ولن تسمح لك Swift باستخدامه كقيمة عادية حتى تفكّه.","استخدم <code dir=\"ltr\">guard let</code> لفكّ القيمة في أعلى الدالة والخروج مبكرًا، فيعمل باقي الدالة بقيمة حقيقية.","استخدم <code dir=\"ltr\">if let</code> حين تحتاج القيمة داخل فرع واحد فقط، و<code dir=\"ltr\">??</code> حين توجد قيمة افتراضية منطقية.","الفكّ القسري (<code dir=\"ltr\">!</code>) يُسقط التطبيق في اللحظة التي تكون فيها القيمة <code dir=\"ltr\">nil</code>؛ اعتبر كل <code dir=\"ltr\">!</code> ادّعاءً يجب أن تستطيع إثباته."],"further":[{"title":"Optionals (The Basics)","url":"https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals"},{"title":"Optional","url":"https://developer.apple.com/documentation/swift/optional"}],"quiz":[{"q":"<p>لديك <code dir=\"ltr\">let note: String? = nil</code>. ما قيمة <code dir=\"ltr\">note?.count ?? 0</code>؟</p>","options":[{"text":"<code dir=\"ltr\">nil</code>","why":"الـ optional chaining ينتج <code dir=\"ltr\">nil</code> هنا فعلًا، لكن <code dir=\"ltr\">?? 0</code> يستبدلها بعد ذلك بالقيمة الافتراضية."},{"text":"<code dir=\"ltr\">0</code>","why":"صحيح. <code dir=\"ltr\">note?.count</code> يتوقّف مبكرًا وينتج <code dir=\"ltr\">nil</code>، ثم يقدّم <code dir=\"ltr\">??</code> القيمة <code dir=\"ltr\">0</code>."},{"text":"يتعطّل البرنامج.","why":"الفكّ القسري (<code dir=\"ltr\">!</code>) وحده يُسقط التطبيق عند <code dir=\"ltr\">nil</code>. أما <code dir=\"ltr\">?.</code> و<code dir=\"ltr\">??</code> فهما الصيغتان الآمنتان."},{"text":"لا يُترجَم، لأنك لا تستطيع استدعاء <code dir=\"ltr\">count</code> على optional.","why":"لا تستطيع استدعاءها مباشرة، لكن <code dir=\"ltr\">?.</code> هي بالضبط الصيغة التي تجعل الاستدعاء مسموحًا."}],"answer":1},{"q":"<p>داخل دالة، بعد <code dir=\"ltr\">guard let amount = parseAmount(text) else { return }</code>، ما الصحيح بشأن <code dir=\"ltr\">amount</code> في السطر التالي؟</p>","options":[{"text":"ما زال من النوع <code dir=\"ltr\">Decimal?</code>، فتحتاج إلى فكّه مرة أخرى.","why":"<code dir=\"ltr\">guard let</code> يربط قيمة غير optional؛ هذا هو الهدف منه أصلًا."},{"text":"لا يوجد إلا داخل كتلة <code dir=\"ltr\">else</code>.","why":"داخل <code dir=\"ltr\">else</code> تكون القيمة غائبة، فلا يوجد ما يُربط أصلًا. أما الاسم الذي يربطه <code dir=\"ltr\">guard let</code> فيبقى متاحًا بعد الـ guard حتى نهاية النطاق."},{"text":"صار قيمة <code dir=\"ltr\">Decimal</code> غير optional تستطيع استخدامها حتى نهاية الدالة.","why":"صحيح. فرع <code dir=\"ltr\">else</code> يجب أن يغادر النطاق، لذلك يعرف الـ compiler أن <code dir=\"ltr\">amount</code> له قيمة بعد ذلك."}],"answer":2},{"q":"<p>متى يكون الفكّ القسري مثل <code dir=\"ltr\">URL(string: &quot;https://example.com&quot;)!</code> مقبولًا؟</p>","options":[{"text":"حين تكون المدخلات قيمة ثابتة كتبتها واختبرتها بنفسك، فتكون <code dir=\"ltr\">nil</code> خطأً من المبرمج.","why":"صحيح. العطل هنا يشير إلى خطأ إملائي في كودك أنت، لا إلى مدخلات سيئة من المستخدم."},{"text":"كلما جاءت القيمة من المستخدم، لأنه عادةً يكتب نصًا صحيحًا.","why":"مدخلات المستخدم هي بالضبط المكان الذي تظهر فيه <code dir=\"ltr\">nil</code>. فكّها باستخدام <code dir=\"ltr\">guard let</code> أو <code dir=\"ltr\">if let</code>."},{"text":"أبدًا؛ فالعامل <code dir=\"ltr\">!</code> أصبح مهجورًا في Swift 6.","why":"العامل <code dir=\"ltr\">!</code> ليس مهجورًا. إنه أداة مقصودة لها حدّ حادّ."},{"text":"كلما أردت كودًا أسرع قليلًا من <code dir=\"ltr\">if let</code>.","why":"لا يوجد فرق يُذكر في السرعة. الاختيار يتعلّق بالأمان."}],"answer":0},{"q":"<p>أيّ سطر يُترجَم حين يكون <code dir=\"ltr\">limit</code> من النوع <code dir=\"ltr\">Int?</code>؟</p>","options":[{"text":"<code dir=\"ltr\">let doubled = limit * 2</code>","why":"لا يمكنك إجراء عمليات حسابية على optional؛ تحتاج Swift أن تعرف ماذا تفعل حين يكون <code dir=\"ltr\">nil</code>."},{"text":"<code dir=\"ltr\">let doubled = (limit ?? 0) * 2</code>","why":"صحيح. العامل <code dir=\"ltr\">??</code> يحوّل <code dir=\"ltr\">Int?</code> إلى <code dir=\"ltr\">Int</code>، فتصبح عملية الضرب واضحة التعريف."},{"text":"<code dir=\"ltr\">let doubled: Int = limit</code>","why":"لا يمكن إسناد <code dir=\"ltr\">Int?</code> إلى <code dir=\"ltr\">Int</code> دون فكّه أولًا."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"swift","code":"import Foundation\n\nfunc addExpense(_ amountText: String, to total: Decimal) -> Decimal {\n    let amount = Decimal(string: amountText)!\n    return total + amount\n}\n\nvar total: Decimal = 0\ntotal = addExpense(\"12.50\", to: total)\ntotal = addExpense(\"\", to: total)   // user tapped Save on an empty field\nprint(total)","bugLines":[4],"title":"اعثر على سبب العطل","prompt":"<p>هذه النسخة المبكرة من منطق إضافة مصروف في Pocket Budget تعمل في كل عرض تجريبي، ثم تتعطّل عند مستخدم حقيقي. اختر السطر الذي يسبّب العطل.</p>\n","hints":["نفّذ الاستدعاء الثاني في ذهنك: ماذا تعيد <code dir=\"ltr\">Decimal(string: &quot;&quot;)</code>؟","ابحث عن الحرف الوحيد الذي يقول «أعدك أن هذه القيمة ليست nil أبدًا».","ستكون رسالة العطل “Unexpectedly found nil while unwrapping an Optional value”."],"explanation":"<p>السطر 4 يفكّ نتيجة <code dir=\"ltr\">Decimal(string:)</code> فكًّا قسريًا. مع نص فارغ تكون النتيجة <code dir=\"ltr\">nil</code>، فيُسقط <code dir=\"ltr\">!</code> التطبيق. اجعل الدالة تعترف بأنها قد تفشل، واترك القرار لمن يستدعيها:</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\"> addExpense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amountText: </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\"> total: Decimal) </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:#CF222E;--shiki-dark:#FF7B72\">    guard</span><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\"> Decimal</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\">: amountText) </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:#CF222E;--shiki-dark:#FF7B72\">        return</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:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> total </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>صار على من يستدعي الدالة أن يفكّ النتيجة، وهذا بالضبط المكان الذي تعرض فيه للمستخدم “Enter an amount” بدل أن يتعطّل التطبيق.</p>\n","checkLabels":{}},"words":635,"readMinutes":4}