ايه هو ال struct وايه هو ال class؟
هما نوع من انواع البيانات data type بيساعدني اجمع شوية صفات ووظايف (properties and methods) مع بعض ويبقو type منفصل قائم بذاته
طيب ايه الفرق بينهم ؟
هكلمكم عن كذا فرق واخر واحد فيهم ممكن يكون مش فرق جوهري بس مهم تقراه
١- القيمة مقابل المرجعية
ال Struct هو value type
يعني هو موجود في الميموري ومحتفظ بقيمته ولو جيت اديه لاي متغير تاني المتغير التاني هياخد قيمته ويحتفظ بيها بشكل مستقل يعني لو غيرت ف المتغير التاني ف المتغير الاول مش هيحس باي تغيير لانه قائم بذاته ومالوش دعوه بحد
ال Class هو refernce type
يعني هو موجود في الميموري وفي pointer بيشاور عليه ولو جيت اديه لاي متغير تاني او مثلا امرره ل function فانا بدي المتغير او بمرر لل function ال pointer ده او ال refrence ده مش بديله ال object نفسه فبالتالي لو غيرت اي حاجه جوا الكلاس المتغيرين هيحسو بيها لانهم بيشاورو علي نفس المكان ف الميموري اللي متخزن فيه ال object
حابه بس اوضح نقطه هنا بخصوص ال struct، احنا قلنا لو اديته لاي متغير تاني المتغير التاني هياخد قيمته ويحتفظ بيها بشكل مستقل ، بس امتي بقي بياخد القيمه بشكل مستقل وامتي بيبقي مشاركها مع المتغير الاولاني او النسخه الاولي ؟
ال Structs بتستخدم حاجة اسمها copy-on-write optimization، طيب دي يعني ايه؟
Copy-on-write (أو COW اختصارًا):
هو نوع من التوفير في ال memory لما يكون عندك Struct (زي Array كبير مثلاً)، وعملت منه نسخة جديدة، بيحصل الآتي:
بدل ما ينسخ كل البيانات في الذاكرة تاني (وده ممكن يكون تقيل)، Copy-on-write بيخلي النسخة الجديدة تشارك نفس البيانات اللي موجودة في النسخة الأصلية من غير ما يعمل نسخة حقيقية منها.
لحد هنا، ما حصلش copy فعلي للبيانات. كل اللي حصل إن النسخة الجديدة بتشارك النسخة الأصلية في نفس البيانات. يعني مافيش اي allocation جديد حصل للفيرجن الجديده.
لكن، لو جيت تعدل أي حاجة في النسخة الجديدة:
هنا، بيحصل النسخ الحقيقي للبيانات (copy) لما حصل تعديل. يعني بياخد البيانات الأصلية ويعمل منها copy جديد، عشان كل نسخة تقدر تبقى مستقلة عن التانية بعد كده.
وده مفيد لو عندك data structure كبيرة ومحتاج تنقلها لأكتر من مكان، بس مش عايز تدفع تكلفة عمل copy غير لما يحصل تعديل فعلاً.
٢- الوراثة
class: بتدعم الوراثة (inheritance).
يعني تقدر تنشئ class جديد يعتمد على class موجود وتضيف أو تعدل في الproperties وال functions.
struct: مش بتدعم الوراثة(inheritance).
مفيش inheritance بين الـ structs، لكن تقدر تستخدم البروتوكولات (protocols) لو عايز تضيف functions مشتركة.
٣- التخصيص في الذاكرة (Memory Allocation)
struct: بيتعمله allocation على الـ stack.
- الـ stack هو جزء من الذاكرة أسرع في ال allocation وال deallocation، والبيانات اللي بتتحط فيه بيتم تحريرها تلقائيًا لما تخرج من النطاق (scope).
- ده بيخلي structs أسرع في الأداء، وده السبب إنك تفضل استخدامها في حالة البيانات البسيطة اللي بتستخدم لفترة قصيرة.
class: بيتعمله allocation على الـ heap.
- الـ heap هو جزء من الذاكرة أبطأ في التعامل وبيحتاج
reference counting (ARC - Automatic Reference Counting) عشان يتحكم في ال lifecycle لل class instances. ال instances اللي بتتحط على heap مش بتتحرر او يتعملها release من الميموري أوتوماتيك ، لازم العدد المرجعي (reference count) يوصل للصفر.
ملحوظة: ال object من ال class بيتحط ف ال heap بس ال reference بتاعه بيتخزن في ال stack ولما ميبقاش في اي reference لل object ده ال ARC بيشيله من ال heap.
ملحوظة:
ال class ليه deinit بس ال struct لا وده منطقي لانه value type ومش بحتاج اهتم بال allocation و deallocation زي ال classوال struct ليه free init انما ال class لا، لازم كل ال params تكون معمولها init والا استخدم ال init لل class
٤- ال Mutability
ال Struct بيكون Immutable
يعني لو عندك struct وعرفته ك let مش var مش هتقدر تعدل علي ال properties بتاعته
ال Class بيكون Mutable
يعني متغير حتي لو معرفه ب let بتقدر عادي تغير ال properties طالما ال properties متعرفه ك var
٥- ال Method Calling
فيه 3 أنواع من الـ Dispatch (ملهاش علاقة بالـ Dispatch Queue اللي بنستخدمها في الـ multithreading):
١- Static Dispatch (بيسموه برضه Direct Dispatch)
٢- Dynamic Dispatch (أو Table Dispatch)
٣- Message Dispatch
ال Struct بيستخدم ال Static Dispatch (الأسرع)
لما تنادي على method من struct (وده نوع Value Type)، الـ compiler بيروح مباشرة لل method وينفذها. ليه؟ لأن الـ compiler عارف إن ال object ده ملوش غير نسخة واحدة (مفيش وراثة ولا تعدد نسخ)، فـ العملية بتبقى سريعة جدًا، وده اسمه Static Dispatch.
ال Class بيستخدم ال Dynamic Dipatch(أبطأ شوية)
لما تنادي على دالة من class (وده نوع Reference Type)، الـ compiler بيعمل حاجة تانية: بيحضر حاجة زي جدول مرجع لكل ال methods اسمه (Witness Table)، وبيحط فيه عناوين الmethods المختلفة اللي ممكن تُنفذ. ولما ننادي على الmethod في وقت التشغيل، الـ compiler بيشوف العنوان الصح في الجدول وينفذه. الموضوع ده بياخد شوية وقت أكتر عشان يشوف الmethod الصح، وده اسمه Dynamic Dispatch.
إيه اللي نستنتجه من كده؟
Struct أسرع وأكتر كفاءة من Class في حالات كتير لأن Static Dispatch أسرع.
لكن لو استخدمنا class ممكن نسرّع العملية ونخلّي الـ compiler يستخدم Static Dispatch بدل Dynamic Dispatch عن طريق إضافة بعض الكلمات زي:
- Final:
لو خليت الـ class أو الmethod يكون final، معناها مفيش وراثة، وده بيسهّل على الـ compiler ينفذها بسرعة.- Private:
لو خليت الmethod بتاعتي private، هتساعد الـ compiler إنه ينفذها مباشرة بدون مايدور في الـ Witness Table.
طيب ايه هيحصل لو كان عندي object من class جوا struct، يعني reference type جوا value type ؟
هو ف الواقع ده بيأثر على طريقة تخزين البيانات في ال memory، وبيخلي الأمور معقدة شوية من ناحية ال stack و ال heap. خليني أوضح: ال struct هو value type، وده يعني إنه بيتخزن عادةً في ال stack، لكن لما struct يحتوي على object من class (reference type)، ال object نفسه بيتخزن في ال heap ، اللي بيتخزن في الـ stack هنا هو مرجع (reference) للـ object اللي في ال heap، مش ال object نفسه.
بمعنى تاني: ال struct في ال stack هيكون جواه pointer (مرجع) بيوصل لل object اللي في ال heap، بدل ما يكون مخزن جواه فعليًا زي باقي متغيرات ال struct العادية.
طب ايه المشاكل اللي ممكن تحصل؟
لما يكون عندك reference type جوا struct، في شوية مشاكل ممكن تحصل:
١- عدم ثبات البيانات (Mutability Issue)
الـ struct بيكون عادة immutable (غير قابل للتغيير) لما يكون let، لكن لو عندك كلاس جوا struct، ممكن تعدّل على الكلاس ده حتى لو struct ثابت ب let. ده بيعمل تضارب مع مفهوم الـ immutability للـ struct.
٢- الـ Copying Issue
لما تنسخ struct فيه object من كلاس، بيحصل نسخ للمرجع بس مش للبيانات نفسها.
يعني لو عدلت في object ال class في نسخة من ال struct، التعديلات هتبقى باينة في كل النسخ التانية اللي بتشير لنفس الكلاس، وده بيعمل سلوك غير متوقع في بعض الحالات.
٣- التسريبات في الذاكرة (Memory Leaks)
لو الكلاس جواه reference قوي (strong reference) لبيانات تانية، ممكن يحصل تسريب للذاكرة memory leak لو النسخ مش بتتحكم كويس في إدارة الذاكرة.
طب إزاي تتفادى المشاكل دي؟
في شوية حلول ممكن تساعدك تتجنب المشاكل دي:
استخدم weak reference: لو مش محتاج تحتفظ بالبيانات بشكل قوي، ممكن تستخدم weak أو unowned بدل ال strong
تجنب reference types جوه structs قدر الإمكان: لو تقدر تخلي الكلاس struct أو تحول البيانات ل value types، ده بيكون أفضل.
استخدم النسخ العميق (Deep Copy): لو محتاج تعمل نسخة منفصلة من البيانات، استخدم طريقة لعمل deep copy عشان تاخد copy من ال object نفسه مش من ال reference بتاعه. وده بيتم بطريقتين
- ال manual
- اني ا conform ل NSCopy
بس طبعا ده بياخد ميموري كتير فحاول تعيط النظر ف ال design بتاعك
اعمل struct بتاعتك immutable: وتأكد إن كل المراجع او ال references اللي فيه مش قابلة للتغيير، وده بيقلل من المخاطر.