مقدمة: لماذا "طول السلسلة النصية" ليس مجرد رقم؟
\n\nتخيل أنك تكتب تطبيقًا لنظام iOS. يقوم المستخدم بإدخال اسمه في حقل نصي. الأمر بسيط؟ ماذا لو كان الاسم يحتوي على رموز تعبيرية (emojis) أو علامات تشكيل (مثل "café") أو أحرف صينية؟ فجأة، تتحول المهمة البسيطة "الحصول على طول السلسلة النصية" إلى تحقيق بوليسي مع عناصر Unicode.
\n\nفي Swift، العمل مع السلاسل النصية ليس مجرد عدّ للأحرف. إنها رحلة إلى عالم مجموعات الحروف الرسومية (grapheme clusters) و Unicode scalars وإرث Objective-C (نعم، NSString لا يزال معنا). إذا كنت قد بدأت للتو في تعلم Swift أو تنتقل من لغات أخرى، فهذه المقالة لك. سنشرح كيفية تحديد طول السلسلة النصية بشكل صحيح، وما هي المزالق التي تنتظرك، ومتى تستخدم count ومتى تستخدم NSString.
هذه المادة مبنية على سؤال شائع حقيقي من Stack Overflow. آلاف المطورين تعثروا في هذا الموضوع. دعونا لا نكرر أخطائهم. هيا بنا!
\n\n\n\n
1. الأساسيات: خاصية count في String
\n\nالطريقة الأكثر وضوحًا للحصول على طول السلسلة النصية في Swift هي استخدام الخاصية count. إنها تُرجع عدد "الأحرف" (Character) في السلسلة النصية. ولكن ما هو الحرف بالنسبة لـ Swift؟
let greeting = "مرحبًا بالعالم!"\nprint(greeting.count) // 12 (بما في ذلك المسافة وعلامة التعجب)\n\nكل شيء يعمل كما هو متوقع. لكن دعنا نعقد المهمة:
\n\nlet emojiString = "👨👩👧👦"\nprint(emojiString.count) // 1 (!!!)\n\nواحد؟ بجدية؟ بينما هي بصريًا عائلة مكونة من أربعة أفراد. ما الخدعة؟
\n\nالإجابة تكمن في كيفية معالجة Swift للسلاسل النصية. count لا يحسب البايتات ولا أحرف Unicode الفردية، بل يحسب مجموعات الحروف الرسومية الممتدة (extended grapheme clusters). وهي عبارة عن تسلسل من واحد أو أكثر من Unicode scalars التي ينظر إليها المستخدم على أنها حرف واحد. الرمز التعبيري "عائلة" يتكون من عدة أجزاء، لكن Swift يعتبرها حرفًا واحدًا، لأن الإنسان يراها كذلك.
هذا السلوك ليس خطأ، بل ميزة. تم تصميم Swift مع مراعاة التدويل. لو كان count يحسب كل Unicode scalar، فإن السلسلة النصية "café" (مع حرف é واحد) قد تعطي نتيجة مختلفة اعتمادًا على كيفية ترميز é: كحرف واحد U+00E9 أو كمجموعة e + علامة تشكيل (U+0065 + U+0301).
let cafe1 = "café" // é كحرف واحد\nlet cafe2 = "cafe\\u{0301}" // e + علامة تشكيل\nprint(cafe1.count) // 4\nprint(cafe2.count) // 4 (على الرغم من وجود 5 scalars في السلسلة)\n\nيعتبر Swift كلا الخيارين كـ 4 أحرف، لأن المستخدم يرى "café" مكونة من 4 أحرف. هذا هو سحر مجموعات الحروف الرسومية.
\n\n\n\n
2. نهج بديل: NSString وخاصية length
\n\nأحيانًا تحتاج إلى الحصول ليس على عدد الأحرف "البشري"، بل على عدد وحدات ترميز UTF-16. على سبيل المثال، عند العمل مع واجهات برمجة تطبيقات قديمة (APIs) أو Core Text أو عند نقل البيانات بتنسيقات تتوقع الطول بـ UTF-16 (مثل بعض بروتوكولات الشبكات).
\n\nهنا يأتي دور NSString — الفئة القديمة الجيدة من Objective-C. يتم ربط (bridge) السلاسل النصية في Swift تلقائيًا بـ NSString، لذا يمكنك استخدام خاصيته length:
let swiftString = "👨👩👧👦"\nlet nsString = swiftString as NSString\nprint(nsString.length) // 11 (عدد وحدات UTF-16)\n\nلماذا 11؟ لأن الرمز التعبيري المركب "عائلة" يشغل 11 وحدة في UTF-16 (كل مكون يُرمَّز كزوج بديل (surrogate pair) أو كحرف منفصل). إذا كنت تعمل مع نص سيتم معالجته بواسطة مكتبات C قديمة أو APIs تتوقع NSString، فاستخدم length. لكن تذكر: هذا ليس عدد الأحرف التي يراها المستخدم.
فارق بسيط آخر: NSString لا يأخذ في الاعتبار مجموعات الحروف الرسومية. بالنسبة للسلسلة النصية "café" (الخيار الثاني)، سيعيد length القيمة 5، بينما count يعيد 4. الخلاصة: اختر الم