{"id":"l-sw-2-1","courseId":"swift-essentials","lang":"ar","kind":"lesson","summary":"توقّع سلوك الـ structs والـ classes عند نسخها، واستخدم الـ mutating methods بشكل صحيح، واكسر دورة الاحتفاظ التي يسبّبها closure باستخدام weak self.","html":"<p>تفتح مصروفًا، وتغيّر مبلغه من 4.50 إلى 5.00 في شاشة التعديل، ثم تنقر Cancel. هل يجب أن تبقى القائمة تعرض 4.50؟ مع الـ struct (البنية) يحدث ذلك تلقائيًا. أما مع الـ class (الصنف)، فالقائمة تعرض 5.00 بالفعل، لأن شاشة التعديل والقائمة كانتا تنظران إلى الكائن نفسه. هذا الفرق الواحد، سلوك <strong>القيمة</strong> مقابل سلوك <strong>المرجع</strong>، يحدّد سلوك تطبيق iOS أكثر من أي قرار تصميمي آخر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ structs تُنسَخ</h2>\n<p>هذا هو نوع المصروف، وخصائصه الآن معرّفة بـ <code dir=\"ltr\">var</code> حتى يمكن تعديله:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Expense.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\"> Expense</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\"> title: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount: Decimal</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\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> original </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</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\">title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</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\">4.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\"> draft </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> original</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">draft.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</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\">(original.amount)   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 4.5</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(draft.amount)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 5</span></span></code></pre></div>\n<p><code dir=\"ltr\">var draft = original</code> تنتج نسخة كاملة مستقلّة. تعديل المسوّدة لا يمكن أن يؤثّر في الأصل، فتستطيع شاشة التعديل أن تعمل على مسوّدة، ولا يكلّف زر Cancel شيئًا: ترمي المسوّدة وحسب. تعطي Swift كل struct مُهيِّئًا مجانيًا اسمه <strong>memberwise initializer</strong>، ولهذا يعمل <code dir=\"ltr\">Expense(title:amount:)</code> دون أن تكتب <code dir=\"ltr\">init</code>.</p>\n<p>النصوص والمصفوفات والقواميس و<code dir=\"ltr\">Decimal</code> و<code dir=\"ltr\">Int</code> كلها structs. لهذا أعطتك <code dir=\"ltr\">var copy = amounts</code> في درس المجموعات مصفوفة مستقلّة. وتجعل Swift هذه النسخ رخيصة بمشاركة التخزين إلى أن يغيّره أحد الطرفين فعلًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ classes تُشارَك</h2>\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\">final</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Account</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\"> balance: Decimal</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    init</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.balance </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> balance</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\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> checking </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Account</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">500</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\"> sameAccount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> checking</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sameAccount.balance </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 50</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\">(checking.balance)          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 450</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(checking </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sameAccount)  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// true: same instance</span></span></code></pre></div>\n<p>الـ instance (النسخة) من class يعيش في مكان واحد؛ والمتغيّرات تحمل مراجع إليه. <code dir=\"ltr\">let sameAccount = checking</code> تنسخ المرجع، فيشير الاسمان إلى الحساب نفسه. والعامل <code dir=\"ltr\">===</code> يسأل «هل هذا هو الـ instance نفسه تمامًا؟»، وهو سؤال لا معنى له مع الـ structs. تحتاج الـ classes إلى <code dir=\"ltr\">init</code> صريح للخصائص المخزّنة التي لا قيم افتراضية لها. علّمها بـ <code dir=\"ltr\">final</code> إلا إذا كنت تخطّط للوراثة منها، وهذا نادر في Swift الحديثة.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-sw-2-1-t1\"><defs><marker id=\"arrow-l-sw-2-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-2-1-t1\">على اليسار، original وdraft صندوقا Expense منفصلان بمبلغين 4.5 و5. على اليمين، checking وsameAccount اسمان يشير سهماهما إلى صندوق Account واحد مشترك رصيده 450.</title>\n  <text class=\"d-label-strong\" x=\"170\" y=\"28\" text-anchor=\"middle\">struct: قيمتان</text>\n  <rect class=\"d-box-success\" x=\"30\" y=\"60\" width=\"130\" height=\"80\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"95\" y=\"90\" text-anchor=\"middle\">original</text>\n  <text class=\"d-label\" x=\"95\" y=\"118\" text-anchor=\"middle\">المبلغ 4.5</text>\n  <rect class=\"d-box-success\" x=\"190\" y=\"60\" width=\"130\" height=\"80\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"255\" y=\"90\" text-anchor=\"middle\">draft</text>\n  <text class=\"d-label\" x=\"255\" y=\"118\" text-anchor=\"middle\">المبلغ 5</text>\n  <text class=\"d-label-muted\" x=\"175\" y=\"175\" text-anchor=\"middle\">نسختان مستقلّتان</text>\n  <path class=\"d-line d-dashed\" d=\"M350 20 L350 230\"/>\n  <text class=\"d-label-strong\" x=\"530\" y=\"28\" text-anchor=\"middle\">class: كائن واحد</text>\n  <rect class=\"d-box\" x=\"390\" y=\"60\" width=\"120\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"450\" y=\"85\" text-anchor=\"middle\">checking</text>\n  <rect class=\"d-box\" x=\"560\" y=\"60\" width=\"130\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"625\" y=\"85\" text-anchor=\"middle\">sameAccount</text>\n  <path class=\"d-arrow\" d=\"M450 100 L515 160\" marker-end=\"url(#arrow-l-sw-2-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M625 100 L560 160\" marker-end=\"url(#arrow-l-sw-2-1-1)\"/>\n  <rect class=\"d-box-primary\" x=\"460\" y=\"165\" width=\"160\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"540\" y=\"190\" text-anchor=\"middle\">Account</text>\n  <text class=\"d-label\" x=\"540\" y=\"212\" text-anchor=\"middle\">الرصيد 450</text>\n</svg><figcaption>نسخ struct مقابل نسخ مرجع إلى class</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">let تعني أشياء مختلفة</h2>\n<p><code dir=\"ltr\">let</code> تجمّد ما يحمله الاسم. في الـ struct يحمل الاسم القيمة كلها، فلا يمكن تغيير أي شيء بداخلها. وفي الـ class يحمل الاسم مرجعًا، فيكون المرجع ثابتًا لكن خصائص الكائن المعرّفة بـ <code dir=\"ltr\">var</code> تبقى قابلة للتغيير، وهذا بالضبط سبب ترجمة <code dir=\"ltr\">sameAccount.balance -= 50</code> رغم أن <code dir=\"ltr\">sameAccount</code> معرّف بـ <code dir=\"ltr\">let</code>.</p>\n<p>الـ methods (الدوال التابعة) في الـ struct التي تغيّر الخصائص يجب أن تصرّح بذلك عبر <code dir=\"ltr\">mutating</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\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Budget</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> spent: 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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> remaining: Decimal { limit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> spent }   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// computed property</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    mutating</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount: Decimal) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        spent </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>\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\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> food </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Budget</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">limit</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">food.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</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\">(food.remaining)   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 258</span></span></code></pre></div>\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>استدعاء mutating method على let</span></p>\n<p><code dir=\"ltr\">let food = Budget(limit: 300)</code> يليه <code dir=\"ltr\">food.record(42)</code> يفشل مع الرسالة “cannot use mutating member on immutable value: ‘food’ is a ‘let’ constant”. والـ compiler محقّ: أنت قلت إن هذه الميزانية لا تتغيّر أبدًا. اجعلها <code dir=\"ltr\">var</code>، أو أعد التفكير في ما إذا كان يجب أن تتغيّر.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">أيّهما تختار</h2>\n<p>توصية Apple، وتوصيتي أنا، هي أن <strong>تبدأ بـ struct</strong>. القيم أسهل في التفكير لأن لا شيء يستطيع تغييرها من وراء ظهرك، وهي آمنة للتمرير بين الـ threads، وهذا مهم جدًا في Swift 6. استخدم class حين تحتاج إلى <strong>هوية</strong>: شيء واحد مشترك تراه أجزاء متعدّدة من التطبيق وتحدّثه. في Pocket Budget، كل <code dir=\"ltr\">Expense</code> هو struct، أما المخزن الذي يملك القائمة وتراقبه عدة شاشات فسيكون class. والماكرو <code dir=\"ltr\">@Observable</code> من إطار Observation و<code dir=\"ltr\">@Model</code> من SwiftData، اللذان ستستخدمهما لاحقًا، يتطلّبان classes لهذا السبب تحديدًا.</p>\n<p>اختبار سريع حين لا تكون متأكّدًا: تخيّل أنك تسلّم نسخة إلى شاشة أخرى. إن كان يجب أن تحصل تلك الشاشة على نسختها الخاصة لتعدّلها بحرية، فأنت تريد struct. وإن كان يجب أن ترى كل تغيير يجريه باقي التطبيق، وأن تظهر تغييراتها في كل مكان، فأنت تريد class. معظم البيانات في معظم التطبيقات تنجح في الاختبار الأول.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">ARC ودورات الاحتفاظ</h2>\n<p>تُحرَّر الـ classes عبر <strong>العدّ التلقائي للمراجع</strong> (ARC): كل مرجع قوي يضيف واحدًا إلى عدّاد، وحين يصل العدّاد إلى الصفر يُحرَّر الكائن ويُنفَّذ <code dir=\"ltr\">deinit</code> الخاص به. تبدأ المشكلة حين يخزّن كائنٌ closure يلتقط الكائن نفسه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">BudgetAlert.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\">final</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> BudgetAlert</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\"> threshold: Decimal</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> onTrigger: (() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    init</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">threshold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.threshold </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> threshold</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\"> arm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        onTrigger </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">weak</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</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:#0550AE;--shiki-dark:#79C0FF\"> self</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</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\">\"Spent more than </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">self</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">threshold</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>\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\">    deinit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </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\">\"BudgetAlert freed\"</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>بدون <code dir=\"ltr\">[weak self]</code>، يمتلك التنبيهُ الـ closure ويمتلك الـ closure التنبيه. لا يصل أيّ من العدّادين إلى الصفر أبدًا، ولا يُنفَّذ <code dir=\"ltr\">deinit</code>، وتكون قد سرّبت ذاكرة. قائمة الالتقاط <code dir=\"ltr\">[weak self]</code> تجعل مرجع الـ closure ضعيفًا واختياريًا، فيفكّه <code dir=\"ltr\">guard let self</code> ما دام الكائن حيًّا. الـ struct وحده لا يستطيع تكوين هذه الدورات، وهذا سبب آخر لكون الـ structs الخيار الافتراضي.</p>\n<p>القيمة أو المرجع نصف عملية التصميم. والنصف الآخر هو وصف بيانات تأتي بأشكال مختلفة، مثل مصروف مدفوع بالبطاقة مقابل آخر مدفوع نقدًا. لهذا وُجدت الـ enums ذات القيم المرتبطة، وهي موضوع الدرس التالي.</p>\n","toc":[{"id":"sec-1","text":"الـ structs تُنسَخ","level":2},{"id":"sec-2","text":"الـ classes تُشارَك","level":2},{"id":"sec-3","text":"let تعني أشياء مختلفة","level":2},{"id":"sec-4","text":"أيّهما تختار","level":2},{"id":"sec-5","text":"ARC ودورات الاحتفاظ","level":2}],"takeaways":["إسناد struct ينتج نسخة مستقلّة؛ أما إسناد instance من class فينتج مرجعًا ثانيًا إلى الكائن نفسه.","الـ struct المعرّف بـ <code dir=\"ltr\">let</code> مجمّد بالكامل، بينما المرجع إلى class المعرّف بـ <code dir=\"ltr\">let</code> ما زال يسمح بتغيير خصائص الكائن المعرّفة بـ <code dir=\"ltr\">var</code>.","الـ methods في الـ struct التي تغيّر الخصائص يجب أن تُعلَّم بـ <code dir=\"ltr\">mutating</code>، ولا تُستدعى إلا على <code dir=\"ltr\">var</code>.","اجعل الـ struct خيارك الافتراضي للبيانات؛ واستخدم class حين تحتاج إلى هوية مشتركة، مثل كائن واحد تراقبه شاشات كثيرة.","الـ class الذي يخزّن closure يلتقط <code dir=\"ltr\">self</code> التقاطًا قويًا ينشئ دورة احتفاظ (retain cycle)؛ التقط <code dir=\"ltr\">[weak self]</code> لكسرها."],"further":[{"title":"Choosing Between Structures and Classes","url":"https://developer.apple.com/documentation/swift/choosing-between-structures-and-classes"},{"title":"Structures and Classes","url":"https://docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures/"},{"title":"Automatic Reference Counting","url":"https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/"}],"quiz":[{"q":"<p><code dir=\"ltr\">Budget</code> هو struct فيه <code dir=\"ltr\">mutating func record(_:)</code>. لماذا لا يُترجَم <code dir=\"ltr\">let food = Budget(limit: 300); food.record(42)</code>؟</p>","options":[{"text":"الـ structs لا يمكن أن تحتوي methods تغيّر الخصائص.","why":"يمكنها ذلك، بشرط أن تُعلَّم الـ method بـ <code dir=\"ltr\">mutating</code> وتُستدعى على <code dir=\"ltr\">var</code>."},{"text":"<code dir=\"ltr\">food</code> معرّف بـ <code dir=\"ltr\">let</code>، فالقيمة كلها غير قابلة للتغيير والـ mutating methods ممنوعة عليها.","why":"صحيح. غيّره إلى <code dir=\"ltr\">var food</code> وسيُترجَم الاستدعاء."},{"text":"يجب أن تُعلَّم الـ method بـ <code dir=\"ltr\">static</code>.","why":"الـ methods من نوع <code dir=\"ltr\">static</code> تنتمي إلى النوع نفسه، لا إلى ميزانية بعينها. هذا يغيّر التصميم ولا يصلح الخطأ."}],"answer":1},{"q":"<p><code dir=\"ltr\">Account</code> هو class. لديك <code dir=\"ltr\">let a = Account(balance: 500); let b = a; b.balance -= 50</code>. ما قيمة <code dir=\"ltr\">a.balance</code>؟</p>","options":[{"text":"450","why":"صحيح. <code dir=\"ltr\">a</code> و<code dir=\"ltr\">b</code> يشيران إلى الكائن نفسه، فالتغيير عبر أحدهما يظهر عبر الآخر."},{"text":"500","why":"هذا سلوك الـ struct. إسناد الـ class ينسخ المرجع، لا الكائن."},{"text":"لا يُترجَم لأن <code dir=\"ltr\">b</code> معرّف بـ <code dir=\"ltr\">let</code>.","why":"<code dir=\"ltr\">let</code> يجمّد المرجع <code dir=\"ltr\">b</code>، لا خصائص الكائن المعرّفة بـ <code dir=\"ltr\">var</code>."}],"answer":0},{"q":"<p>أيّ البيانات في Pocket Budget هي الأنسب لأن تكون class بدل struct؟</p>","options":[{"text":"مصروف واحد له عنوان ومبلغ وتاريخ.","why":"المصروف بيانات بسيطة لا هوية لها سوى قيمها؛ والـ struct يُبقي النسخ مستقلّة وآمنة."},{"text":"زوج من تاريخَي بداية ونهاية لتقرير.","why":"البيانات الصغيرة غير القابلة للتغيير هي المثال النموذجي للـ struct."},{"text":"المخزن المشترك الوحيد الذي تقرأ منه عدة شاشات وتحدّثه.","why":"صحيح. عدة شاشات تحتاج إلى رؤية الـ instance نفسه، وهذا بالضبط ما يمنحه لك سلوك المرجع."},{"text":"رمز عملة مثل “EUR”.","why":"الرمز قيمة؛ يناسبه struct أو حتى <code dir=\"ltr\">String</code> عادي أكثر."}],"answer":2},{"q":"<p>يخزّن class القيمة <code dir=\"ltr\">onTrigger = { print(self.threshold) }</code> في إحدى خصائصه. ما النتيجة؟</p>","options":[{"text":"يحصل الـ closure على نسخة من <code dir=\"ltr\">self</code>، فلا تظهر له التغييرات على الكائن.","why":"الـ classes لا تُنسخ أبدًا بالالتقاط. الـ closure يحمل مرجعًا قويًا إلى الكائن نفسه."},{"text":"ترفض Swift ترجمة أي closure يذكر <code dir=\"ltr\">self</code>.","why":"Swift تُلزمك فقط بكتابة <code dir=\"ltr\">self.</code> صراحةً في الـ escaping closures؛ ولا تمنع التقاطه."},{"text":"لا شيء خاص؛ يحرّر ARC الاثنين عند إغلاق الشاشة.","why":"لا يستطيع ARC تحرير كائنات يُبقي كلٌّ منها الآخر حيًّا. هذه هي المشكلة كلها."},{"text":"الكائن والـ closure يُبقي كلٌّ منهما الآخر حيًّا، فلا يُحرَّر الكائن أبدًا.","why":"صحيح. التقط <code dir=\"ltr\">[weak self]</code> حتى لا يمتلك الـ closure الكائن."}],"answer":3}],"exercise":{"kind":"choice","options":["a","b","c","d"],"correct":["b"],"title":"struct أم class: توقّع الناتج","prompt":"<p><code dir=\"ltr\">Limit</code> هو struct و<code dir=\"ltr\">Wallet</code> هو class. اقرأ المقطع وتوقّع السطر الوحيد الذي يطبعه.</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\">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\"> Limit</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\"> amount: Decimal</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\">final</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Wallet</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\"> balance: Decimal</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    init</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.balance </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> balance }</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\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> food </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Limit</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\">300</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\"> fun </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> food</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">fun.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</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\"> mine </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Wallet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">balance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">500</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\"> shared </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mine</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">shared.balance </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 50</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\">(food.amount, fun.amount, mine.balance)</span></span></code></pre></div>\n","hints":["تعامل مع النصفين كلٌّ على حدة: الـ struct أولًا، ثم الـ class.","بعد <code dir=\"ltr\">var fun = food</code>، كم قيمة من النوع <code dir=\"ltr\">Limit</code> توجد؟","بعد <code dir=\"ltr\">let shared = mine</code>، كم كائنًا من النوع <code dir=\"ltr\">Wallet</code> يوجد؟"],"explanation":"<p><code dir=\"ltr\">var fun = food</code> تنسخ الـ struct، فتغيير <code dir=\"ltr\">fun.amount</code> إلى 100 يترك <code dir=\"ltr\">food.amount</code> على 300. أما <code dir=\"ltr\">let shared = mine</code> فتنسخ المرجع فقط: ما زال هناك <code dir=\"ltr\">Wallet</code> واحد، وطرح 50 عبر <code dir=\"ltr\">shared</code> يظهر عبر <code dir=\"ltr\">mine</code>. الناتج هو <code dir=\"ltr\">300 100 450</code>.</p>\n<p>لاحظ أن <code dir=\"ltr\">shared</code> معرّف بـ <code dir=\"ltr\">let</code> ومع ذلك تُرجمت عملية الطرح. <code dir=\"ltr\">let</code> تثبّت الكائن الذي يشير إليه <code dir=\"ltr\">shared</code>، لا خصائص الكائن المعرّفة بـ <code dir=\"ltr\">var</code>.</p>\n","checkLabels":{},"optionText":{"a":{"text":"<code dir=\"ltr\">100 100 450</code>","why":"هذا يعامل الـ struct كأنه class. <code dir=\"ltr\">var fun = food</code> أنشأت نسخة منفصلة، فيحتفظ <code dir=\"ltr\">food</code> بالقيمة 300."},"b":{"text":"<code dir=\"ltr\">300 100 450</code>","why":"صحيح. نسخة الـ struct مستقلّة، ومرجعا الـ class يتشاركان محفظة واحدة."},"c":{"text":"<code dir=\"ltr\">300 100 500</code>","why":"هذا يعامل الـ class كأنه struct. <code dir=\"ltr\">shared</code> و<code dir=\"ltr\">mine</code> هما الكائن نفسه، فتختفي الـ 50 من كليهما."},"d":{"text":"لا يُترجَم: <code dir=\"ltr\">shared</code> معرّف بـ <code dir=\"ltr\">let</code>.","why":"<code dir=\"ltr\">let</code> يجمّد المرجع، لا خصائص الكائن المعرّفة بـ <code dir=\"ltr\">var</code>، لذلك <code dir=\"ltr\">shared.balance -= 50</code> مسموح."}}},"words":632,"readMinutes":4}