👩👩👧👦: رمز واحد يسبب الجنون
\n\nتخيل: أنت تكتب تطبيقاً لنظام iOS، وتعالج النص، وتحسب الأحرف. كل شيء يعمل بشكل مثالي، حتى تصادف الرمز التعبيري لعائلة مكونة من ثلاث نساء وفتاتين. يبدو وكأنه رمز واحد على الشاشة، لكن Swift يقول إنه... 4 رموز؟ أم 7؟ أو ربما 11؟ ألم مألوف؟ مرحباً بك في عالم Unicode وسلاسل Swift!
\n\nهذا السؤال هو أحد أكثر الأسئلة شيوعاً على Stack Overflow في قسم Swift. وليس من دون سبب. فالبساطة الظاهرية للسلاسل تتحول إلى مهمة شاقة حقيقية عندما تدخل الرموز التعبيرية المركبة على الخط. في هذه المقالة، لن نشرح فقط لماذا يحدث هذا، بل سنوضح أيضاً كيفية العمل بشكل صحيح مع هذه السلاسل في Swift. إذا كنت تكتب بلغة Swift وتواجه معالجة إدخال المستخدم أو الرموز التعبيرية أو التدويل، فهذه المادة من أجلك.
\n\n🔍 المشكلة: "كم عدد الأحرف في هذه السلسلة؟"
\n\nلنذهب مباشرة إلى صلب الموضوع. افتح Xcode Playground واكتب:
\n\nimport Foundation\n\nlet familyEmoji = "👩👩👧👦"\nprint(familyEmoji.count) // 1? 4? 7?\n\n\nإذا كنت تتوقع 1، فأنت على صحيح من وجهة نظر المستخدم. إذا كنت تتوقع 4 (أربعة أشكال) فهذا منطقي أيضاً. وستعطيك Swift 1. لكن جرب familyEmoji.unicodeScalars.count وستحصل على 7! وإذا حللتها إلى utf16.count فستكون 11.
ما الخدعة؟ الرمز التعبيري 👩👩👧👦 هو مجموعة رسومية مركبة (Composite Grapheme Cluster). يتكون من عدة عدديات Unicode (Unicode Scalars) متحدة بأحرف ربط غير مرئية (ZWJ - Zero Width Joiner). تعتبر Swift هذه التسلسلات رمزاً مرئياً واحداً (Grapheme)، وهو أمر صحيح من وجهة نظر المستخدم، لكنه يربكك عندما تحاول العمل مع "داخل" السلسلة.
\n\n🧱 تشريح الرمز التعبيري: العدديات والمجموعات وZWJ
\n\nلفهم السحر، دعنا نحلل الرمز التعبيري إلى مكوناته. كل حرف في Unicode هو عددي (Scalar) (رمز رقمي). بالنسبة لـ 👩👩👧👦، يبدو التسلسل كالتالي:
\n\nlet scalars = familyEmoji.unicodeScalars\nfor scalar in scalars {\n print(scalar.value, scalar.properties.name ?? "unknown")\n}\n// الإخراج (مبسط):\n// 128105 (WOMAN)\n// 8205 (ZERO WIDTH JOINER)\n// 128105 (WOMAN)\n// 8205 (ZERO WIDTH JOINER)\n// 128103 (GIRL)\n// 8205 (ZERO WIDTH JOINER)\n// 128102 (BOY)\n\n\nأترى؟ أربعة أشكال (امرأتان، فتاة، صبي) متصلة بثلاثة رموز ZWJ غير مرئية (الكود 8205). تتبع Swift معيار Unicode، وتفسر هذا التسلسل بأكمله كـ مجموعة رسومية موسعة (Extended Grapheme Cluster) واحدة. ولهذا السبب يعيد .count القيمة 1 - هذا هو عدد "الأحرف من وجهة نظر المستخدم".
🔄 مقارنة الأساليب: count مقابل unicodeScalars مقابل utf16
\n\nدعنا ننظم الأمور. بالنسبة للسلسلة "👩👩👧👦" نحصل على ثلاث إجابات مختلفة اعتماداً على API الذي نستخدمه:
- \n
.count(المجموعات الرسومية) —1. صحيح لواجهة المستخدم، والعرض في UILabel. \n .unicodeScalars.count(عدديات Unicode) —7. مفيد لمعالجة النص منخفضة المستوى. \n .utf16.count(وحدات كود UTF-16) —11. يُستخدم للتوافق مع Objective-C NSString. \n .utf8.count(بايتات UTF-8) —25. للعمل مع الشبكة والملفات. \n
كل من هذه الأساليب يحل مشكلة معينة. خطأ المبتدئ هو استخدام .count لتقييد طول الإدخال في حقل نصي (على سبيل المثال، "لا يزيد عن 140 حرفاً"). بالنسبة للمستخدم، "👩👩👧👦" هو حرف واحد، والحد الأقصى البالغ 140 من هذه الرموز التعبيرية أمر طبيعي. لكن إذا كنت تخزن السلسلة في قاعدة بيانات بطول ثابت بالبايتات، فأنت بحاجة إلى .utf8.count.