ITMS-90338: حل خطأ Non-public API usage في Expo
بقلم Mehdi Azizi
نُشر في ، 9 د
يعني ITMS-90338 أن البناء يشير إلى دالة أو طريقة أو صنف تعتبره Apple خاصًا. حدّد المكتبة المسؤولة وحدّثها، ثم أعد البناء.
ITMS-90338 فحص تلقائي تجريه Apple على البناء (build) الذي ترفعه. يخبرك أن ملفك الثنائي يشير إلى واجهات API تعتبرها Apple خاصة: طرق Objective-C أو دوال أو أصناف. الحل: حدّد الملف المذكور في البريد الإلكتروني، وتتبّع الأمر حتى تصل إلى التبعية التي تحتوي الاسم، ثم حدّثها أو أعد تسمية طريقتك، وبعدها أعد البناء.
مع Expo، يأتي الاسم في الغالب من مكتبة أصلية (native)، ونادرًا من كود JavaScript الخاص بك. في 15 أكتوبر 2026، شغّلت أوامر التحليل الواردة في هذا الدليل على بناء محاكي لتطبيق Solys، منبّهي الذي يحتوي ألعابًا صغيرة (Expo SDK 57)، وعلى iOS SDK الخاص بـ Xcode 26.2. إليك كيف تقرأ البريد، وتجد المسؤول، وتتعرّف على الإنذار الكاذب.
ماذا يعني الخطأ ITMS-90338؟
عنوان الرسالة هو "ITMS-90338: Non-public API usage". وله ثلاثة أشكال، بحسب ما وجدته Apple في ملفك الثنائي:
- "references non-public selectors": أسماء طرق Objective-C، مثل
boldFontOfSize:؛ - "references non-public symbols": دوال، مكتوبة بشرطة سفلية في أولها، مثل
__dyld_get_shared_cache_range؛ - "contains or inherits from non-public classes": اسم صنف.
كل شكل يعطي مسارًا، ثم قائمة الأسماء. ويضيف النص نصيحتين: أعد تسمية طرقك إذا تطابق اسمها مع API خاصة، وأزل المكتبة الثابتة التي تحتويها. وفي بريد من سبتمبر 2026، تقترح Apple أيضًا xcrun swift-demangle لجعل أسماء Swift المرمّزة مقروءة.
القاعدة وراء هذا الفحص هي الإرشاد 2.5.1: لا يجوز للتطبيق استخدام سوى واجهات API العامة. واتفاقية ترخيص Apple Developer Program تكرّر ذلك في البند 3.3.1: لا استدعاء لأي API خاصة. أما المراجعة البشرية لهذه القاعدة فتجدها مفصّلة في دليل الإرشاد 2.5.1. هنا نعالج خطأ الرفع.
كما في أخطاء ITMS الأخرى، لم يفتح أحد تطبيقك: حلّلت Apple الملف الثنائي أثناء معالجة البناء. لا تنشر Apple أي قائمة بواجهات API الخاصة: الأسماء الواردة في بريدك هي القائمة الوحيدة الموثوقة.
قراءة المسار: SDK أم ملفك التنفيذي؟
المسار المكتوب قبل القائمة يخبرك في أي ملف تبحث.
Frameworks/Nom.framework/Nom: الاسم موجود في إطار عمل ديناميكي مضمَّن، أي في SDK محدد. في بناء Solys، يحتوي هذا المجلد 14 إطار عمل: Hermes وReact Native ووحدات Expo مُجمَّعة مسبقًا ومكتبة الصور SDWebImage.- اسم تطبيقك وحده: الاسم موجود في الملف التنفيذي الرئيسي. مع Expo، يجمع هذا الملف كودك الأصلي والـ pods المُجمَّعة من مصادرها، مربوطة ربطًا ثابتًا افتراضيًا. وبالتالي قد تكون أي مكتبة أصلية هي المسؤولة.
الشجرة: من الاسم المذكور إلى الحل
خذ الأسطر بالترتيب. أول سطر يطابق حالتك يعطيك الاتجاه.
| ما تقرؤه | أين تبحث | الحل |
|---|---|---|
اسم يبدأ بـ $s أو _$s | xcrun swift-demangle | أعد المرور على الشجرة |
| غير موجود في ملف IPA | grep على الحزمة | أعد رفع بناء |
| اسم عام على iOS | ترويسات iOS SDK | أعد رفع بناء |
API_UNAVAILABLE(ios) | ترويسات iOS SDK | حدّث الحزمة |
في Frameworks/X | الـ SDK الذي يوفّر X | حدّث الـ SDK |
| selector، في تطبيقك | الـ pods والوحدات الأصلية | أضف بادئة أو حدّث |
رمز _nom، في تطبيقك | nm -m، ثم الـ pods | استبدل الاستدعاء |
| صنف، في تطبيقك | أصنافك والـ pods | أضف بادئة إلى الصنف |
الأسطر التي تقول "أعد رفع بناء" تشير إلى إنذار كاذب، نتناوله لاحقًا.
الخطوة 1: افتح البناء المرفوع
اعمل على الملف الذي أُرسل فعلًا إلى Apple، لا على مشروعك. نزّل ملف IPA من صفحة البناء على expo.dev، ثم شغّل على جهاز Mac:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
السطر الثاني يعرض أطر العمل المضمَّنة. والثالث يبحث عن ربط مباشر بإطار عمل خاص من Apple: يجب ألا يعرض شيئًا. في Solys، لا يجد otool -L أي إطار عمل خاص، لا في الملف التنفيذي ولا في الأطر الـ 14.
الخطوة 2: جد الملف الذي يحتوي الاسم
ابدأ بنطاق واسع. ابحث عن الاسم المذكور، من دون الشرطات السفلية في أوله، في الحزمة كلها:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
يعرض الأمر الملفات التي تحتوي هذا النص. بعدها دقّق بحسب نوع الاسم (selector أو رمز أو صنف)، على الملف الذي وجدته، والمشار إليه هنا بـ Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
مع الـ selector، استخدم strings لا nm. على إطار React في Solys، يعرض nm الطرق مع صنفها، مثل -[RCTRootView initWithBridge:moduleName:initialProperties:]، ولا يعرض أبدًا الـ selector initWithBridge: وحده. أما strings فيجده.
مع الرمز، اقرأ نهاية السطر. في Solys، يعرض nm -m مثلًا (undefined) external __dyld_get_image_name (from libSystem): الملف الثنائي يستدعي الدالة من دون أن يحتويها، وlibSystem هي التي توفّرها. وهذه الدالة بالذات عامة، كما تبيّن الخطوة التالية.
الخطوة 3: تحقّق هل الاسم خاص فعلًا
ترويسات iOS SDK تصف واجهات API العامة. بحث يستغرق ثواني يخبرك هل الاسم المذكور موجود فيها، وعلى أي منصات. مع الرمز، احذف أولًا الشرطة السفلية التي تضاف عند التجميع: _SCDynamicStoreCreate تصبح SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
في 15 أكتوبر 2026، ومع iOS SDK 26.2، هذه نتيجة أربعة أسماء مأخوذة من رسائل ITMS-90338 حقيقية:
| الاسم المذكور | في iOS SDK | التفسير |
|---|---|---|
| _dyld_get_shared_cache_range | غير موجود | API خاصة |
| boldFontOfSize: | غير موجود | خاصة أو تصادم أسماء |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | macOS فقط |
| isMainFrame | خاصية في WebKit | عامة |
الاسم الموجود والمتاح على iOS يشير إلى إنذار كاذب. والاسم غير الموجود يشير إلى API خاصة أو تصادم أسماء. هناك حدّان: الـ API العامة في Swift فقط لا تظهر في هذه الترويسات، ومع دالة وصول مثل setDelegate:، ابحث عن الخاصية delegate.
الخطوة 4: تتبّع الأمر حتى الحزمة
إذا ذكر المسار إطار عمل، فاسمه يقودك إلى الـ pod، ثم إلى حزمة npm:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
يخبرك npm explain بالتبعية التي ثبّتت الحزمة، إن لم تكن قد أضفتها بنفسك.
وإذا ذكر المسار الملف التنفيذي، فابحث عن الاسم في المصادر الأصلية، ثم في الـ pods:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
عدّل قائمة المجلدات حسب مشروعك: modules هو مجلد وحدات Expo المحلية. يظهر المجلد ios/Pods بعد npx expo prebuild -p ios، الذي يثبّت الـ pods أيضًا على Mac. انتبه: قد يعيد هذا الأمر إنشاء مجلد ios. إذا كنت قد عدّلته يدويًا، فاعمل على نسخة منه.
البحث في ios/Pods لا يقلّ أهمية عن البحث في المصادر. في Solys، يحتوي هذا المجلد 15 إطار عمل مُجمَّعة مسبقًا، بصيغة .xcframework: وحدات Expo وHermes وReact Native وSDKs من أطراف ثالثة. والبحث المقتصر على ملفات المصدر سيفوّت كودها.
الأسباب المعتادة
SDK من طرف ثالث، قديم جدًا أو حديث جدًا. في سبتمبر 2026، أبلغ مطوّر Flutter عن رفض سببه SDK أمني: كان إصداره 8.2.3 يستدعي __dyld_get_shared_cache_range. الإصدار 8.2.2 كان يمرّ، ونصح الناشر بالعودة إليه ريثما يصدر إصلاح.
وحدة أصلية تستدعي API غير موثّقة. قد تستخدم وحدتك أنت، أو إضافة، أو مكتبة صغيرة، طريقة داخلية في UIKit وُجدت في منتدى ما. والفحص يكتشفها من اسمها.
تصادم أسماء. طريقتك أو صنفك يحمل، بالصدفة، اسم API خاصة من Apple. الرسالة تتوقّع هذا وتطلب منك إعادة التسمية. توصي Apple بـإضافة بادئة إلى الطرق التي تضيفها إلى أصنافها، مثلًا ثلاثة أحرف صغيرة تليها شرطة سفلية.
API عامة على macOS، لا على iOS. في 2024، رُفض تطبيق .NET MAUI بسبب _SCDynamicStoreCreate. توثّق Apple هذه الدالة فعلًا، لكن لـ macOS فقط، وترويسة iOS SDK تصنّفها API_UNAVAILABLE(ios, tvos, watchos).
صحّح ولا تتحايل
- حدّث التبعية. للحزم التي تتابعها Expo، يشير
npx expo install --checkإلى الحزم التي لا تتوافق مع الـ SDK الخاص بك، بحسب توثيق Expo CLI. وللحزم الأخرى، اقرأ سجل الإصدارات. - ارجع إلى الإصدار السابق إذا كان الإصدار الجديد هو الذي أدخل الاسم، وأبلغ الناشر.
- استبدل أو أزل المكتبة المهجورة.
- أعد تسمية طريقتك أو صنفك، مع بادئة.
لا تُخفِ الاسم. تقطيع سلسلة نصية أو استدعاء الطريقة بطريق ملتوٍ يخفي الاستخدام ولا يصحّحه. يبقى محظورًا بموجب القاعدة 2.5.1 والاتفاقية، وحينها يصبح حسابك هو المعرّض للخطر.
بعد ذلك، أعد البناء مع رقم بناء جديد. التحديث عن بُعد لا يكفي: الاسم موجود في الكود الأصلي المُجمَّع. قبل الرفع، أعد تنفيذ الخطوة 2 على ملف IPA الجديد. يجب ألا يظهر الاسم بعد الآن.
عندما يكون إنذارًا كاذبًا
فحص Apple يخطئ أحيانًا. في 6 يونيو 2022، تلقّت تطبيقات React Native وFlutter الخطأ ITMS-90338 بسبب أسماء عامة من WebKit، مثل setNavigationDelegate: أو isMainFrame، حتى في تحديثات شبه خالية من التغييرات. في اليوم التالي، كتب مهندس من Apple أن المشكلة حُلّت من جهة التحقق، ودعا إلى إعادة رفع البناءات. والنقاش محفوظ في مستودع Expo على GitHub.
ثلاث علامات مجتمعة تشير إلى إنذار كاذب:
- البناء يشبه آخر بناء قُبل؛
- الخطوة 2 لا تجد الاسم، أو الخطوة 3 تُظهر أنه عام على iOS؛
- مطوّرون آخرون يبلغون عن الخطأ نفسه في اليوم نفسه على منتديات Apple.
في هذه الحالة، راجع حالة خدمات Apple، ثم أعد رفع بناء برقم جديد. إذا استمر الخطأ، فراسل دعم المطوّرين لدى Apple مع البريد الكامل. أما تصادم الأسماء فليس إنذارًا كاذبًا في نظر Apple: أعد التسمية.
قبل رفعك القادم
لأخطاء ITMS الأخرى أدلّتها أيضًا: ITMS-90725 لإصدار Xcode، وITMS-91053 لملف الخصوصية (privacy manifest)، وITMS-90034 للتوقيع. بعد قبول البناء، ثبّته عبر TestFlight قبل المراجعة.
أما الرفض من مراجع بشري فيحتاج نظرة أوسع: الخطوات موجودة في صفحة تطبيق رفضته Apple، وأكثر الأسباب شيوعًا في لماذا ترفض Apple تطبيقًا.
لتتحقّق من ملفك قبل الرفع، يجمع Review Kit قائمة التحقق الخاصة بي ومهارات (skills) لـ Claude Code تراجع مشروعك. وإذا تعثّر رفعك وأردت نظرة بشرية، احجز ساعة معي: نراجع مشروعك عبر مكالمة فيديو، على شاشتك. وإذا كنت تحضّر لإطلاقك الأول، أجرِ التشخيص المجاني: يرتّب لك الخطوات ويرسل إليك قائمة التحقق الخاصة بي لتجنّب رفض Apple.
الأسئلة الشائعة
هل ITMS-90338 رفض من App Review؟
لا. إنه فحص تلقائي يجري أثناء معالجة البناء، قبل أي مراجعة بشرية. البناء المرفوض لا يصل إلى TestFlight ولا إلى المراجعة. أما الـ API الخاصة التي يكتشفها مراجع لاحقًا فتندرج تحت الإرشاد 2.5.1.
هل يمكن إصلاح ITMS-90338 بتحديث عن بُعد؟
لا. الأسماء المذكورة موجودة في الكود الأصلي المُجمَّع داخل الملف الثنائي. التحديث عن بُعد لا يغيّر سوى JavaScript والموارد. صحّح التبعية، وأعد البناء، ثم ارفعه من جديد.
لماذا تذكر Apple طرقًا لم أكتبها قط؟
لأنها تأتي من مكتبة مضمَّنة، أو من تصادم أسماء: تبعية تعرّف طريقة تحمل اسم API خاصة. تقرأ Apple الأسماء الموجودة في الملف الثنائي، لا كودك المصدري. تتبّع الأمر حتى الحزمة باستخدام grep وnpm explain.
كيف أعرف هل API ما عامة على iOS؟
ابحث عن اسمها في ترويسات iOS SDK، التي يعطيك xcrun --sdk iphoneos --show-sdk-path مسارها، ثم اقرأ مدى توفّرها. صفحة توثيق Apple تذكر ذلك أيضًا. الدالة الموثّقة لـ macOS فقط ليست عامة على iPhone.
هل يجب تغيير رقم البناء بعد التصحيح؟
نعم. دع EAS يزيده تلقائيًا: الرقم الذي سبق رفعه للإصدار نفسه قد يُرفض بالخطأ ITMS-90189.

