الفرق بين المراجعتين ل"ReactNative/native modules ios"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
ط
 
(9 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE: الوحدات الأصيلة (iOS) في React Native}}</noinclude>
+
<noinclude>{{DISPLAYTITLE: وحدات iOS الأصيلة في React Native}}</noinclude>
 
يُرجى الاطّلاع أولًا على صفحة [[ReactNative/native modules intro|مدخل إلى الوحدات الأصيلة Native Modules]] للتعرّف على الوحدات الأصيلة.
 
يُرجى الاطّلاع أولًا على صفحة [[ReactNative/native modules intro|مدخل إلى الوحدات الأصيلة Native Modules]] للتعرّف على الوحدات الأصيلة.
  
== إنشاء وحدة التقويم الأصيلة كمثال Calendar Native Module ==
+
== إنشاء وحدة تقويم أصيلة كمثال Calendar Native Module ==
 
سننشئ وحدة أصيلة هي الوحدة <code>CalendarModule</code> التي ستسمح بالوصول إلى واجهات برمجة تقويم Apple البرمجية من شيفرة JavaScript، وستتمكّن في النهاية من استدعاء التابع <code>CalendarModule.createCalendarEvent('Dinner Party', 'My House');‎</code> من JavaScript ، أي ستستدعي تابعًا أصيلًا ينشئ حدث التقويم.<blockquote>يعمل فريق React Native حاليًا على إعادة بناء نظام الوحدات الأصيلة، ويُطلَق على هذا النظام الجديد اسم TurboModules الذي سيساعد في تسهيل إنشاء اتصال أكثر كفاءة ومن النوع الآمن بين شيفرة JavaScript والشيفرة الأصيلة، دون الاعتماد على جسر React Native، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من [https://github.com/react-native-community/discussions-and-proposals/issues/40 هنا]). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.</blockquote>
 
سننشئ وحدة أصيلة هي الوحدة <code>CalendarModule</code> التي ستسمح بالوصول إلى واجهات برمجة تقويم Apple البرمجية من شيفرة JavaScript، وستتمكّن في النهاية من استدعاء التابع <code>CalendarModule.createCalendarEvent('Dinner Party', 'My House');‎</code> من JavaScript ، أي ستستدعي تابعًا أصيلًا ينشئ حدث التقويم.<blockquote>يعمل فريق React Native حاليًا على إعادة بناء نظام الوحدات الأصيلة، ويُطلَق على هذا النظام الجديد اسم TurboModules الذي سيساعد في تسهيل إنشاء اتصال أكثر كفاءة ومن النوع الآمن بين شيفرة JavaScript والشيفرة الأصيلة، دون الاعتماد على جسر React Native، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من [https://github.com/react-native-community/discussions-and-proposals/issues/40 هنا]). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.</blockquote>
  
 
=== الإعداد ===
 
=== الإعداد ===
 
افتح أولًا مشروع iOS داخل تطبيق React Native الخاص بك في Xcode. يمكنك العثور على مشروع iOS الخاص بك داخل تطبيق React Native كما في الشكل التالي:
 
افتح أولًا مشروع iOS داخل تطبيق React Native الخاص بك في Xcode. يمكنك العثور على مشروع iOS الخاص بك داخل تطبيق React Native كما في الشكل التالي:
[[ملف:native-modules-ios-open-project.png|بديل=native modules ios open project|مركز|تصغير|344x344بك|مكان العثور على مشروع iOS الخاص بك]]
+
[[ملف:native-modules-ios-open-project.png|بديل=native modules ios open project|مركز|تصغير|680x680px]]
  
 +
نوصيك باستخدام Xcode لكتابة شيفرتك الأصيلة، إذ بُنِي Xcode لتطوير تطبيقات iOS، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.
  
نوصيك باستخدام Xcode لكتابة شيفرتك الأصيلة، حيث بُنِي Xcode لتطوير تطبيقات iOS، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.
+
=== إنشاء ملفات الوحدة الأصيلة المخصصة ===
 
 
=== إنشاء ملفات الوحدة الأصيلة المخصَّصة ===
 
 
تتمثل الخطوة الأولى في إنشاء ترويسة الوحدة الأصيلة المخصَّصة وملفات التنفيذ. أنشئ ملفًا جديدًا بالاسم <code>RCTCalendarModule.h</code> كما يلي:
 
تتمثل الخطوة الأولى في إنشاء ترويسة الوحدة الأصيلة المخصَّصة وملفات التنفيذ. أنشئ ملفًا جديدًا بالاسم <code>RCTCalendarModule.h</code> كما يلي:
[[ملف:native-modules-ios-add-class.png|بديل=native modules ios add class|مركز|تصغير|330x330بك|إنشاء ملف وحدة أصيلة مخصصَّة ضمن نفس مجلد AppDelegate]]
+
[[ملف:native-modules-ios-add-class.png|بديل=native modules ios add class|مركز|تصغير|680x680px|إنشاء ملف وحدة أصيلة مخصصَّة ضمن نفس مجلد AppDelegate]]
وأضِف ما يلي إلى هذا الملف:<syntaxhighlight lang="obj-c">
+
وأضِف ما يلي إلى هذا الملف:<syntaxhighlight lang="objective-c">
 
//  RCTCalendarModule.h
 
//  RCTCalendarModule.h
 
#import <React/RCTBridgeModule.h>
 
#import <React/RCTBridgeModule.h>
 
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
 
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
 
@end
 
@end
</syntaxhighlight>يمكنك استخدام أيّ اسم يناسب الوحدة الأصيلة التي تنشئها، إذ يمكنك تسمية الصنف <code>RCTCalendarModule</code> بما أنك تنشئ وحدة تقويم أصيلة. لا تحتوي لغة ObjC دعمًا على مستوى اللغة لفضاءات الأسماء مثل لغتي Java أوC++‎، لذلك يجب أن سبق سلسلةٌ نصية فرعية اسمَ الصنف، وقد تكون هذه السلسلة النصية اختصارًا لاسم تطبيقك أو لاسم بنيته التحتية، إذ تشير RCT في هذا المثال إلى React.
+
</syntaxhighlight>يمكنك استخدام أيّ اسم يناسب الوحدة الأصيلة التي تنشئها، إذ يمكنك تسمية الصنف <code>RCTCalendarModule</code> بما أنك تنشئ وحدة تقويم أصيلة. لا تحتوي لغة ObjC دعمًا على مستوى اللغة لفضاءات الأسماء مثل لغتي Java أوC++ ‎، لذلك يجب أن تسبق سلسلةٌ نصية فرعية اسمَ الصنف، وقد تكون هذه السلسلة النصية اختصارًا لاسم تطبيقك أو لاسم بنيته التحتية، إذ تشير RCT في هذا المثال إلى [[React]].
  
يطبّق الصنفُ <code>CalendarModule</code> بروتوكولَ <code>RCTBridgeModule</code> كما سترى أدناه، فالوحدة الأصيلة هي صنف Objective-C الذي يطبّق بروتوكول <code>RCTBridgeModule</code>.
+
يطبّق الصنفُ <code>CalendarModule</code> بروتوكولَ <code>RCTBridgeModule</code> كما سترى، فالوحدة الأصيلة هي صنف Objective-C يطبّق بروتوكول <code>RCTBridgeModule</code>.
  
لنبدأ بعد ذلك في تطبيق الوحدة الأصيلة. أنشئ ملف التنفيذ المقابل <code>RCTCalendarModule.m</code> في نفس المجلد وضمِّن ما يلي:<syntaxhighlight lang="obj-c">
+
لنبدأ بعد ذلك في تطبيق الوحدة الأصيلة. أنشئ ملف التنفيذ المقابل <code>RCTCalendarModule.m</code> في نفس المجلد وضمِّن ما يلي:<syntaxhighlight lang="objective-c">
 
// RCTCalendarModule.m
 
// RCTCalendarModule.m
 
#import "RCTCalendarModule.h"
 
#import "RCTCalendarModule.h"
سطر 39: سطر 38:
 
تشتمل حاليًا الوحدة الأصيلة <code>RCTCalendarModule.m</code> فقط على الماكرو <code>RCT_EXPORT_MODULE</code> والذي يصدّر ويسجّل صنف الوحدة الأصيلة باستخدام React Native. يأخذ الماكرو <code>RCT_EXPORT_MODULE</code> أيضًا وسيطًا اختياريًا يحدّد الاسم الذي يمكن الوصول من خلاله إلى الوحدة كما في شيفرة JavaScript الخاصة بك.
 
تشتمل حاليًا الوحدة الأصيلة <code>RCTCalendarModule.m</code> فقط على الماكرو <code>RCT_EXPORT_MODULE</code> والذي يصدّر ويسجّل صنف الوحدة الأصيلة باستخدام React Native. يأخذ الماكرو <code>RCT_EXPORT_MODULE</code> أيضًا وسيطًا اختياريًا يحدّد الاسم الذي يمكن الوصول من خلاله إلى الوحدة كما في شيفرة JavaScript الخاصة بك.
  
ليس هذا الوسيط قيمة حرفية للسلسلة النصية، إذ مُرِّر اسم الوحدة بهذا الشكل <code>RCT_EXPORT_MODULE (CalendarModuleFoo)</code> وليس <code>RCT_EXPORT_MODULE("CalendarModuleFoo")</code> في المثال التالي:<syntaxhighlight lang="obj-c">
+
ليس هذا الوسيط قيمة حرفية للسلسلة النصية، إذ مُرِّر اسم الوحدة بهذا الشكل <code>RCT_EXPORT_MODULE (CalendarModuleFoo)</code> وليس <code>RCT_EXPORT_MODULE("CalendarModuleFoo")</code> في المثال التالي:<syntaxhighlight lang="objective-c">
 
// ‫لتصدير وحدة بالاسم CalendarModuleFoo
 
// ‫لتصدير وحدة بالاسم CalendarModuleFoo
 
RCT_EXPORT_MODULE(CalendarModuleFoo);
 
RCT_EXPORT_MODULE(CalendarModuleFoo);
 
</syntaxhighlight>يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JS كما يلي:<syntaxhighlight lang="javascript">
 
</syntaxhighlight>يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JS كما يلي:<syntaxhighlight lang="javascript">
 
const { CalendarModuleFoo } = ReactNative.NativeModules;
 
const { CalendarModuleFoo } = ReactNative.NativeModules;
</syntaxhighlight>إذا لم تحدد اسمًا، فسوف يتطابق اسم وحدة JavaScript مع اسم صنف Objective-C، مع إزالة البادئات مثل "RCT" أو "RK".
+
</syntaxhighlight>إذا لم تحدد اسمًا، فسوف يتطابق اسم وحدة JavaScript مع اسم صنف Objective-C، مع إزالة البادئات مثل "RCT" أو "RK". سنستدعي الآن <code>RCT_EXPORT_MODULE</code> دون وسطاء. ستظهر الوحدة لإطار عمل React Native بالاسم <code>CalendarModule</code>، بما أنه اسم صنف Objective-C، ولكن دون RCT.<syntaxhighlight lang="objective-c">
 
 
لنستدعي الآن <code>RCT_EXPORT_MODULE</code> دون وسطاء. ستظهر الوحدة لإطار عمل React Native بالاسم <code>CalendarModule</code>، بما أنه اسم صنف Objective-C، ولكن دون RCT.<syntaxhighlight lang="obj-c">
 
 
// ‫سيؤدي عدم تمرير الاسم إلى تصدير اسم الوحدة الأصيلة كاسم صنف Objective-C دون "RCT"
 
// ‫سيؤدي عدم تمرير الاسم إلى تصدير اسم الوحدة الأصيلة كاسم صنف Objective-C دون "RCT"
 
RCT_EXPORT_MODULE();
 
RCT_EXPORT_MODULE();
سطر 53: سطر 50:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== تصدير طريقة أصيلة إلى JavaScript ===
+
=== تصدير تابع أصيل إلى شيفرة JavaScript ===
لن يعرض React Native أي عمليات في الوحدة الأصيلة لشيفرة JavaScript ما لم يُطلَب ذلك صراحةً، ويمكن ذلك باستخدام الماكرو <code>RCT_EXPORT_METHOD</code>. التوابع المكتوبة في الماكرو <code>RCT_EXPORT_METHOD</code> غير متزامنة، وبالتالي يكون نوع القيمة المُعادة دائمًا void. يمكن تمرير نتيجة من تابع الماكرو <code>RCT_EXPORT_METHOD</code> إلى شيفرة JavaScript  باستخدام توابع رد النداء callbacks أو إرسال الأحداث (التي سنتحدث عنها لاحقًا). لنبدأ الآن بإعداد تابع أصيل للوحدة الأصيلة <code>CalendarModule</code> باستخدام الماكرو <code>RCT_EXPORT_METHOD</code>. أطلِق على هذا التابع الاسم <code>createCalendarEvent()‎</code> واجعله يأخذ حاليًا وسيطَي الاسم name والموقع location كسلاسل نصية (سنتكلم عن خيارات نوع الوسيط لاحقًا أيضًا).<syntaxhighlight lang="javascript">
+
لن يعرض React Native أي عمليات في الوحدة الأصيلة لشيفرة JavaScript ما لم يُطلَب منه ذلك صراحةً، ويمكن ذلك باستخدام الماكرو <code>RCT_EXPORT_METHOD</code>. التوابع المكتوبة في الماكرو <code>RCT_EXPORT_METHOD</code> غير متزامنة، وبالتالي يكون نوع القيمة المُعادة دائمًا void. يمكن تمرير نتيجة من تابع الماكرو <code>RCT_EXPORT_METHOD</code> إلى شيفرة JavaScript  باستخدام توابع رد النداء callbacks أو إرسال الأحداث (التي سنتحدث عنها لاحقًا). لنبدأ الآن بإعداد تابع أصيل للوحدة الأصيلة <code>CalendarModule</code> باستخدام الماكرو <code>RCT_EXPORT_METHOD</code>. أطلِق على هذا التابع الاسم <code>createCalendarEvent()‎</code> واجعله يأخذ حاليًا وسيطَي الاسم name والموقع location كسلاسل نصية (سنتكلم عن خيارات نوع الوسيط لاحقًا أيضًا).<syntaxhighlight lang="objective-c">
 
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
 
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
 
{
 
{
 
}
 
}
</syntaxhighlight><blockquote>'''ملاحظة''': لن يكون الماكرو <code>RCT_EXPORT_METHOD</code> ضروريًا مع نظام TurboModules إلّا إن اعتمد تابعك على تحويل وسيط RCT (اطّلع على أنواع الوسطاء أدناه). سيزيل React Native في النهاية <code>RCT_EXPORT_MACRO</code>، لذلك لا نشجّعك على استخدام <code>RCTConvert</code>، بل يمكنك إجراء تحويل الوسيط ضمن جسم التابع.</blockquote>أضِف سجل وحدة التحكم console log في التابع قبل بناء وظائف التابع <code>createCalendarEvent()‎</code>، لتتمكّن من تأكيد أن التابع اُستدعِي من شيفرة JavaScript في تطبيق React Native الخاص بك. استخدم واجهات الترويسة  <code>RCTLog</code> البرمجية من React. استورد هذه الترويسة في أعلى ملفك ثم أضِف استدعاء السجل كما يلي:<syntaxhighlight lang="javascript">
+
</syntaxhighlight><blockquote>'''ملاحظة''': لن يكون الماكرو <code>RCT_EXPORT_METHOD</code> ضروريًا مع نظام TurboModules إلّا إن اعتمد تابعك على تحويل وسيط RCT (اطّلع على أنواع الوسطاء أدناه). سيزيل React Native في النهاية <code>RCT_EXPORT_MACRO</code>، لذلك لا نشجّعك على استخدام <code>RCTConvert</code>، بل يمكنك إجراء تحويل الوسيط ضمن جسم التابع.</blockquote>أضِف سجل وحدة التحكم console log في التابع قبل بناء وظائف التابع <code>createCalendarEvent()‎</code>، لتتمكّن من تأكيد أن التابع اُستدعِي من شيفرة JavaScript في تطبيق React Native الخاص بك. استخدم واجهات الترويسة  <code>RCTLog</code> البرمجية من React. استورد هذه الترويسة في أعلى ملفك ثم أضِف استدعاء السجل كما يلي:<syntaxhighlight lang="objective-c">
 
#import <React/RCTLog.h>
 
#import <React/RCTLog.h>
 
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
 
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
سطر 67: سطر 64:
  
 
=== التوابع المتزامنة ===
 
=== التوابع المتزامنة ===
يمكنك استخدام <code>RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD</code> لإنشاء تابع أصيل متزامن كما يلي:<syntaxhighlight lang="javascript">
+
يمكنك استخدام <code>RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD</code> لإنشاء تابع أصيل متزامن كما يلي:<syntaxhighlight lang="objective-c">
 
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
 
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
 
{
 
{
 
return [[UIDevice currentDevice] name];
 
return [[UIDevice currentDevice] name];
 
}
 
}
</syntaxhighlight>يجب أن يكون نوع القيمة المُعادة لهذا التابع من نوع الكائن (معرّف id) ويجب أن يكون قابلًا للتسلسل إلى JSON، أي أن الخطّاف hook يمكنه فقط أن يعيد قيم nil أو قيم JSON (مثل NSNumber و NSString و NSArray و NSDictionary).
+
</syntaxhighlight>يجب أن يكون نوع القيمة المُعادة لهذا التابع من نوع الكائن (معرّف id) ويجب أن يكون قابلًا للتسلسل إلى JSON، أي أن الخطّاف hook يمكنه فقط أن يعيد قيم nil أو JSON (مثل NSNumber و NSString و NSArray و NSDictionary).
  
لا نوصي حاليًا باستخدام توابع متزامنة، لأن استدعاء التوابع المتزامن يمكن أن يكون لها عواقب أداء ويدخل أخطاءً مرتبطة بالخيوط إلى وحداتك الأصيلة. لاحظ أيضًا أنه إذا اخترت استخدام <code>RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD</code>، فلن يتمكن تطبيقك من استخدام مصحّح أخطاء Google Chrome مرة أخرى، لأن التوابع المتزامنة تتطلب آلة JS الافتراضية لمشاركة الذاكرة مع التطبيق. بينما بالنسبة لمصحّح أخطاء Google Chrome، فإن React Native يعمل داخل آلة JS الافتراضية في Google Chrome، ويتواصل بصورة غير متزامنة مع الأجهزة المتنقلة عبر WebSockets.
+
لا نوصي حاليًا باستخدام توابع متزامنة، لأن استدعاء التوابع المتزامن يمكن أن يكون له عواقب على الأداء ويمكن أن يدخل أخطاءً مرتبطة بالخيوط إلى وحداتك الأصيلة. لاحظ أيضًا أنه إذا اخترت استخدام <code>RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD</code>، فلن يتمكن تطبيقك من استخدام منقّح أخطاء Google Chrome مرة أخرى، لأن التوابع المتزامنة تتطلب آلة JS الافتراضية لمشاركة الذاكرة مع التطبيق. بينما بالنسبة لمنقّح أخطاء Google Chrome، فإن React Native يعمل داخل آلة JS الافتراضية في Google Chrome، ويتواصل تواصلًا غير متزامن مع الأجهزة المتنقلة عبر WebSockets.
  
 
=== اختبر ما بنيته ===
 
=== اختبر ما بنيته ===
سطر 98: سطر 95:
  
 
export default NewModuleButton;
 
export default NewModuleButton;
</syntaxhighlight>يجب استيراد <code>NativeModules</code> من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة JavaScript:<syntaxhighlight lang="javascript">
+
</syntaxhighlight>يجب استيراد <code>NativeModules</code> من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة [[JavaScript]]:<syntaxhighlight lang="javascript">
 
import { NativeModules } from 'react-native';
 
import { NativeModules } from 'react-native';
 
</syntaxhighlight>ثم يمكنك الوصول إلى الوحدة الأصيلة <code>CalendarModule</code> من خارج <code>NativeModules</code>:<syntaxhighlight lang="javascript">
 
</syntaxhighlight>ثم يمكنك الوصول إلى الوحدة الأصيلة <code>CalendarModule</code> من خارج <code>NativeModules</code>:<syntaxhighlight lang="javascript">
 
const { CalendarModule } = NativeModules;
 
const { CalendarModule } = NativeModules;
</syntaxhighlight>يمكنك الآن استدعاء التابع الأصيل <code>createCalendarEvent()‎</code> بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى التابع <code>onPress()‎</code> في المكوّن <code>NewModuleButton</code>:<syntaxhighlight lang="javascript">
+
</syntaxhighlight>يمكنك الآن استدعاء التابع الأصيل <code>createCalendarEvent()‎</code> بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى تابع <code>onPress()‎</code> في المكوّن <code>NewModuleButton</code>:<syntaxhighlight lang="javascript">
 
const onPress = () => {
 
const onPress = () => {
 
   CalendarModule.createCalendarEvent('testName', 'testLocation');
 
   CalendarModule.createCalendarEvent('testName', 'testLocation');
سطر 117: سطر 114:
  
 
يجب أن ترى رسالة <code>RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);‎</code> في كل مرة تستدعي فيها تابع الوحدة الأصيلة.
 
يجب أن ترى رسالة <code>RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);‎</code> في كل مرة تستدعي فيها تابع الوحدة الأصيلة.
[[ملف:native-modules-ios-logs.png|بديل=native modules ios logs|مركز|تصغير|373x373بك|سجلات iOS في Flipper]]
+
[[ملف:native-modules-ios-logs.png|بديل=native modules ios logs|مركز|تصغير|680x680px|سجلات iOS في Flipper]]
أنشأت حتى الآن وحدة iOS أصيلة واستدعيت تابعًا لها من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد توابع رد النداء والوعود ضمن وحدتك الأصيلة.
+
أنشأت حتى الآن وحدة iOS أصيلة واستدعيت تابعًا لها من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد توابع رد النداء callbacks و<nowiki/>[[JavaScript/Promise/Using promises|الوعود promises]] ضمن وحدتك الأصيلة.
  
 +
== ما بعد وحدة التقويم الأصيلة ==
  
تحتاج التطبيقات أحيانًا إلى الوصول إلى واجهة برمجة (API) منصةٍ ما، وReact Native لا يحتوي على وحدة ملائمة لهذا الغرض حتى الآن. قد ترغب في إعادة استخدام بعض شيفرات Objective-C أو Swift أو C++‎ الموجودة مسبقا دون الحاجة إلى إعادة كتابتها بلغة JavaScript أو كتابة بعض الشيفرات عالية الأداء أو متعددة السلاسل (multi-threaded) مثل معالجة الصور أو قاعدة بيانات أو أي عدد من الإضافات المتقدمة.
+
=== تصدير الوحدة الأصيلة الأفضل ===
 +
يُعَد استيراد وحدتك الأصيلة عن طريق سحبها من <code>NativeModules</code> كما ذكرنا سابقًا أمرًا صعبًا بعض الشيء. يمكنك إنشاء مغلِّف JavaScript للوحدة، للحفاظ على مستهلكي وحدتك الأصيلة الخاصة من الحاجة إلى تنفيذ هذا الأمر الصعب في كل مرة يريدون فيها الوصول إلى وحدتك الأصيلة. أنشئ ملف JavaScript جديد باسم NativeCalendarModule.js يحتوي على ما يلي:<syntaxhighlight lang="javascript">
 +
/**
 +
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
 +
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:
  
صُمِّم React Native بحيث يمكنك كتابة شيفرة أصيلة حقيقية مع التحكم الكامل بالمنصة. هذه ميزة أكثر تقدمًا ولا نتوقع أن تكون جزءًا من عملية التطوير المعتادة، ولكن وجودها ضروري. إذا كان React Native لا يدعم ميزة أصيلة تحتاجها، يجب أن تكون قادرًا على بنائها بنفسك.
+
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
 
+
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
هذا دليل أكثر تقدمًا يوضح كيفية إنشاء وحدة أصيلة. يُفترَض أن القارئ يعرف لغة Objective-C  أو Swift والمكتبات الأساسية (Foundation، وUIKit).
+
*/
 
+
import { NativeModules } from 'react-native';
==إعداد الوحدات الأصيلة==
+
const { CalendarModule } = NativeModules;
عادة ما تُوزَّع الوحدات الأصيلة كحزم npm، إلا أنه لكي تكون وحداتٍ أصيلة، فستحتوي على مكتبة Xcode. للحصول على الهيكل الأساسي، تأكد من قراءة [[ReactNative/native modules setup|دليل إعداد الوحدات الأصيلة]] أولاً.
+
export default CalendarModule;
 
 
==وحدة تقويم iOS كمثال ‎(iOS Calendar Module)‎==
 
سيستخدم هذا الدليل واجهة برمجة [https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/EventKitProgGuide/Introduction/Introduction.html تقويم iOS] كمثال. لنفترض أننا نريد الوصول إلى تقويم iOS من JavaScript.
 
 
 
الوحدات  الأصيلة مجرد أصناف Objective-C تُنفِّذ (أو تعتمد على) بروتوكول <code>RCTBridgeModule</code>. المقطع <code>RCT</code>  اختصارٌ لكلمة ReaCT.
 
 
 
<syntaxhighlight lang="objective-c">
 
// CalendarManager.h
 
#import <React/RCTBridgeModule.h>
 
 
 
@interface CalendarManager : NSObject <RCTBridgeModule>
 
@end
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
يصبح ملف JavaScript هذا أيضًا موقعًا جيدًا لإضافة أي عمليات من جانب شيفرة JavaScript. إذا استخدمت مثلًا نظام أنواع مثل [[TypeScript]]، فيمكنك إضافة تعليقات الأنواع للوحدة الأصيلة في هذا الملف. لا يدعم React Native حتى الآن أمان النوع Native to JS، ولكنّ جميع شيفرات JS الخاصة بك ستكون من النوع الآمن. ستسهّل هذه التعليقات عليك التبديل إلى الوحدات الأصيلة ذات النوع الآمن باستمرار. يوضّح المثال التالي إضافة النوع الآمن إلى وحدة التقويم Calendar Module:<syntaxhighlight lang="javascript">
بالإضافة إلى تطبيق بروتوكول <code>RCTBridgeModule</code>، يجب أن يتضمن صنفك أيضًا الماكرو ‎‎<code>RCT_EXPORT_MODULE()</code>‎‎، الذي يأخذ معاملا اختياريا يحدد الاسم الذي سيتم الوصول به إلى الوحدة في شيفرة JavaScript الخاصة بك (المزيد حول هذا لاحقًا). إذا لم تحدد اسمًا، فسيتطابق اسم وحدة JavaScript مع اسم صنف Objective-C. إذا بدأ اسم صنف Objective-C بالمقطع RCT، فستستثني JavaScript البادئة <code>RCT</code> من اسم الوحدة.
+
/**
 
+
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
<syntaxhighlight lang="objective-c">
+
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:
// CalendarManager.m
+
*
#import "CalendarManager.h"
+
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
 
+
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
@implementation CalendarManager
+
*/
 
+
import { NativeModules } from 'react-native';
// لتصدير وحدةٍ باسم
+
const { CalendarModule } = NativeModules
// CalendarManager
+
interface CalendarInterface {
RCT_EXPORT_MODULE();
+
  createCalendarEvent(name: string, location: string): void;
 
+
}
 
+
export default CalendarModule as CalendarInterface;
// سيُسمِّي هذا السّطر الوحدةَ بالاسم
 
// AwesomeCalendarManager
 
// RCT_EXPORT_MODULE(AwesomeCalendarManager);
 
 
 
@end
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
يمكنك في ملفات JavaScript الأخرى الوصول إلى الوحدة الأصيلة واستدعاء تابعها كما يلي:<syntaxhighlight lang="javascript">
 +
import NativeCalendarModule from './NativeCalendarModule';
 +
NativeCalendarModule.createCalendarEvent('foo', 'bar');
 +
</syntaxhighlight><blockquote>لاحظ أن هذا المثال افترض أن المكان الذي تستورده منه <code>CalendarModule</code> موجود ضمن تسلسل الملف <code>CalendarModule.js</code> الهرمي نفسه، لذلك يجب أن  تحدّث مسار الاستيراد النسبي عند الحاجة.</blockquote>
  
لن يوفر React Native الوصول إلى أي تابع من توابع الصنف <code>CalendarManager</code> للغة JavaScript ما لم يُعلَم بذلك بوضوح. وذلك باستخدام الماكرو ‎‎<code>RCT_EXPORT_METHOD()</code>‎‎:
+
=== أنواع الوسطاء ===
 +
يحوّل React Native الوسطاء من كائنات JS إلى مثيلاتها في لغة Objective-C أو Swift عند استدعاء تابع الوحدة الأصلية في شيفرة JavaScript . إذا قَبِل تابع وحدة Objective-C الأصيلة وسيطًا من النوع NSNumber على سبيل المثال، فستحتاج في JS إلى استدعاء التابع باستخدام وسيط من النوع عدد number، إذ سيتوّلى React Native عملية التحويل نيابةً عنك. فيما يلي قائمة بأنواع الوسطاء المدعومة لتوابع الوحدات الأصيلة ومقابلاتها في JavaScript التي ستُربَط معها:
 +
{| class="wikitable"
 +
!OBJECTIVE-C
 +
!JAVASCRIPT
 +
|-
 +
|NSString
 +
|string, ?string سلسلة نصية
 +
|-
 +
|BOOL
 +
|boolean قيمة منطقية
 +
|-
 +
|NSNumber
 +
|‎?boolean قيمة منطقية
 +
|-
 +
|double
 +
|number عدد
 +
|-
 +
|NSNumber
 +
|‎?number عدد
 +
|-
 +
|NSArray
 +
|Array, ?Array مصفوفة
 +
|-
 +
|NSDictionary
 +
|Object, ?Object كائن
 +
|-
 +
|RCTResponseSenderBlock
 +
|Function (success) دالة (نجاح)
 +
|-
 +
|RCTResponseSenderBlock, RCTResponseErrorBlock
 +
|Function (failure) دالة (فشل)
 +
|-
 +
|RCTPromiseResolveBlock, RCTPromiseRejectBlock
 +
|Promise وعد
 +
|}
 +
<blockquote>
 +
الأنواع التالية مدعومة حاليًا ولكن لن يدعمها نظام TurboModules، لذلك يُرجَى تجنب استخدامها:
  
<syntaxhighlight lang="objective-c">
+
* Function (failure) -> RCTResponseErrorBlock دالة (فشل)
#import "CalendarManager.h"
+
* Number -> NSInteger عدد
#import <React/RCTLog.h>
+
* Number -> CGFloat عدد
 +
* Number -> float عدد</blockquote>يمكنك أيضًا في نظام التشغيل iOS كتابة توابع الوحدة الأصيلة باستخدام أي نوع وسيط يدعمه الصنف <code>RCTConvert</code> (اطّلع على [https://github.com/facebook/react-native/blob/master/React/Base/RCTConvert.h RCTConvert] للحصول على تفاصيل حول ما يدعمه هذا الصنف). تقبل جميع دوال مساعد RCTConvert قيمة JSON كدخل وتربطه مع صنف أو نوع Objective-C أصيل.
  
@implementation CalendarManager
+
=== تصدير الثوابت ===
 
+
يمكن للوحدة الأصيلة تصدير الثوابت عن طريق إعادة كتابة التابع الأصيل <code>constantsToExport()‎</code>. سيُعاد كتابة التابع <code>constantsToExport()‎</code> الذي يعيد قاموسًا Dictionary يحتوي على خاصية اسم الحدث الافتراضي الذي يمكنك الوصول إليه في شيفرة JavaScript كما يلي:<syntaxhighlight lang="javascript">
RCT_EXPORT_MODULE();
+
- (NSDictionary *)constantsToExport
 
 
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
 
 
{
 
{
  RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
+
return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
 
}
 
}
 
+
</syntaxhighlight>يمكن بعد ذلك الوصول إلى الثابت عن طريق استدعاء <code>getConstants()‎</code> ضمن الوحدة الأصيلة في شيفرة JS كما يلي:<syntaxhighlight lang="javascript">
@end
+
const { DEFAULT_EVENT_NAME } = CalendarModule.getConstants();
</syntaxhighlight>
+
console.log(DEFAULT_EVENT_NAME);
يمكنك الآن استدعاء التابع من ملف JavaScript الخاص بك بهذا الشكل:
 
<syntaxhighlight lang="javascript">
 
import {NativeModules} from 'react-native';
 
var CalendarManager = NativeModules.CalendarManager;
 
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
====ملاحظة حول أسماء توابع JavaScript====
+
يمكن الوصول إلى الثوابت المُصدَّرة في التابع <code>constantsToExport()‎</code> مباشرةً من الكائن <code>NativeModule</code>، ولكن لن يدعم نظام TurboModules ذلك، لذلك نشجّع المجتمع على التبديل إلى الطريقة السابقة لتجنّب التهجير الضروري باستمرار.<blockquote>لاحظ أن الثوابت تُصدَّر فقط في وقت التهيئة initialization time، لذلك إذا غيّرت قيم التابع <code>constantsToExport()‎</code> في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript.</blockquote>إذا أعدّت كتابة <code>constantsToExport()‎</code> بالنسبة لنظام iOS، فيجب عليك أيضًا تطبيق ‎<code>+ requiresMainQueueSetup</code> للسماح لإطار عمل React Native بمعرفة ما إذا كانت وحدتك بحاجة إلى التهيئة على الخيط الرئيسي قبل تنفيذ أي شيفرة JavaScript، وإلّا فسترى تحذيرًا من أن وحدتك ستُهيَّأ في المستقبل على خيط في الخلفية إن لم تلغِ الاشتراك صراحةً من ‎<code>+ requiresMainQueueSetup:‎</code>. إن لم تتطلّب وحدتك الخاصة الوصول إلى UIKit، فيجب أن ترد على ‎<code>+ requiresMainQueueSetup</code> بالرد لا NO.
اسم التابع المُصدَّر إلى JavaScript هو اسم التابع الأصيل إلى غاية أول نقطتين (‎‎<code>:</code>‎‎). يعرِّف React Native أيضًا ماكرو يُسمى ‎‎<code>RCT_REMAP_METHOD()</code>‎‎ لتحديد اسم تابع JavaScript. هذا مفيدٌ عندما تكون توابع أصيلة متعددة هي نفسها إلى غاية أول نقطتين وفي حالة تضمّنت أسماء JavaScript متعارضة.
 
  
تُهيَّأ وحدة <code>CalendarManager</code> في جهة لغة Objective-C باستخدام استدعاء ‎‎<code>[CalendarManager new]</code>‎‎. النوع المُعاد (return type) لتوابع الجسر (bridge methods) يكون دائمًا <code>void</code>.
+
=== دوال رد النداء Callbacks ===
 +
تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات الذي يتمثّل في دوال رد النداء Callbacks. تُستخدَم دوال رد النداء لتمرير البيانات من شيفرة Objective-C إلى شيفرة JavaScript للتوابع غير المتزامنة، ويمكن استخدامها أيضًا لتنفيذ شيفرة JS تنفيذًا غير متزامن من الجانب الأصيل.
  
جسر React Native غير متزامن (asynchronous)، لذا فإن الطريقة الوحيدة لتمرير نتيجة إلى JavaScript هي باستخدام دوال رد النداء (callbacks) أو بعث الأحداث (emitting events)، انظر أدناه للمزيد.
+
تُطبَّق دوال رد النداء بالنسبة لنظام iOS باستخدام النوع <code>RCTResponseSenderBlock</code>. فيما يلي أُضيف معامل رد النداء <code>myCallBack</code> إلى التابع <code>createCalendarEventMethod()‎</code>:<syntaxhighlight lang="objective-c">
 +
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
 +
                location:(NSString *)location
 +
                myCallback:(RCTResponseSenderBlock)callback)
 +
</syntaxhighlight>ثم يمكنك بعد ذلك استدعاء دالة رد النداء في دالتك الأصيلة، مع توفير أي نتيجة تريد تمريرها إلى JavaScript في مصفوفة. لاحظ أن النوع <code>RCTResponseSenderBlock</code> يقبل وسيطًا واحدًا فقط، وهو مصفوفة من المعاملات لتمريرها إلى دالة رد نداء JavaScript. سنمرّر أدناه معرّف ID الحدث الذي أُنشِئ في استدعاء سابق.<blockquote>يجب التركيز على أن دالة رد النداء لا تُستدعَى فور اكتمال الدالة الأصيلة، لأن الاتصال غير متزامن.</blockquote><syntaxhighlight lang="objective-c">
 +
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
 +
{
 +
NSInteger eventId = ...
 +
callback(@[@(eventId)]);
  
==أنواع المعاملات==
+
RCTLogInfo(@"Pretending to create an event %@ at %@", title, location);
 +
}
  
يدعم <code>RCT_EXPORT_METHOD</code> جميع أنواع كائنات JSON القياسية، مثل:
+
</syntaxhighlight>يمكن بعد ذلك الوصول إلى هذا التابع في JavaScript كما يلي:<syntaxhighlight lang="javascript">
 
+
const onSubmit = () => {
* السلاسل النصيّة (<code>NSString</code>)
+
  CalendarModule.createCalendarEvent(
* الأعداد (<code>NSInteger</code>, <code>float</code>, <code>double</code>, <code>CGFloat</code>, <code>NSNumber</code>)
+
    'Party',
* القيم المنطقيّة (<code>BOOL</code>, <code>NSNumber</code>)
+
    '04-12-2020',
* المصفوفات (<code>NSArray</code>) التي تحتوي على أي نوع من قائمة الأنواع هذه.
+
    (eventId) => {
* الكائنات (<code>NSDictionary</code>) ذات مفاتيحٍ نصيّة وقيمٍ من أي نوع من قائمة الأنواع هذه.
+
      console.log(`Created a new event with id ${eventId}`);
* الدوال (<code>RCTResponseSenderBlock</code>)
+
    }
   
+
  );
إضافة إلى دعم أي نوع من الأنواع التي يدعمها الصنف <code>RCTConvert</code> (انظر [https://github.com/facebook/react-native/blob/master/React/Base/RCTConvert.h RCTConvert] للمزيد). تقبل جميع دوال <code>RCTConvert</code> المساعِدةُ قيمةَ JSON كمدخلات وتربطها بنوع أو صنف Objective-C أصيل.
+
};
 
+
</syntaxhighlight>يُفترَض أن تستدعي الوحدة الأصيلة دالة رد النداء الخاصة بها مرة واحدة فقط، ولكن يمكنها تخزين دالة رد النداء واستدعاؤها لاحقًا. يُستخدَم هذا النمط غالبًا لتغليف واجهات iOS البرمجية التي تتطلب مفوضين delegates (اطّلع على <code>[https://github.com/facebook/react-native/blob/3a11f0536ea65b87dc0f006665f16a87cfa14b5e/React/CoreModules/RCTAlertManager.mm RCTAlertManager]</code> كمثال). إذا لم تُستدعَى دالة رد النداء مطلقًا، فسيُسرَّب جزء من الذاكرة. هناك طريقتان لمعالجة أخطاء دوال رد النداء، فالطريقة الأولى هي اتباع عُرف Node ومعاملة الوسيط الأول الذي يُمرَّر إلى مصفوفة رد النداء ككائن خطأ.<syntaxhighlight lang="objective-c">
في مثالنا <code>CalendarManager</code>، نحتاج إلى تمرير تاريخ الحدث إلى التابع الأصيل. لا يمكننا إرسال كائنات تاريخ JavaScript (كائنات ‎‎<code>Date</code>‎‎) عبر الجسر، لذلك نحتاج إلى تحويل التاريخ إلى سلسلة نصيّة أو عدد. يمكننا كتابة دالتنا الأصيلة هكذا:
+
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
<syntaxhighlight lang="objective-c">
 
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)secondsSinceUnixEpoch)
 
 
{
 
{
   NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch];
+
   NSNumber *eventId = [NSNumber numberWithInt:123];
 +
  callback(@[[NSNull null], eventId]);
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight>ثم يمكنك في شيفرة JavaScript التحقق من أن الوسيط الأول قد مرّر خطأً أم لا كما يلي:<syntaxhighlight lang="javascript">
 
+
const onPress = () => {
أو هكذا:
+
  CalendarModule.createCalendarEventCallback(
<syntaxhighlight lang="objective-c">
+
    'testName',
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString)
+
    'testLocation',
 +
    (error, eventId) => {
 +
      if (error) {
 +
        console.error(`Error found! ${error}`);
 +
      }
 +
      console.log(`event id ${eventId} returned`);
 +
    }
 +
  );
 +
};
 +
</syntaxhighlight>أما الطريقة الثانية هي استخدام دالتي رد نداء منفصلتين هما: onFailure و onSuccess.<syntaxhighlight lang="objective-c">
 +
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
 +
                  location:(NSString *)location
 +
                  errorCallback: (RCTResponseSenderBlock)errorCallback
 +
                  successCallback: (RCTResponseSenderBlock)successCallback)
 
{
 
{
   NSDate *date = [RCTConvert NSDate:ISO8601DateString];
+
   @try {
}
+
    NSNumber *eventId = [NSNumber numberWithInt:123];
</syntaxhighlight>
+
    successCallback(@[eventId]);
 +
  }
  
ولكن باستخدام ميزة التحويل التلقائي للأنواع، فيمكننا تخطي خطوة التحويل اليدوي بالكامل، وفقط كتابة ما يلي:
+
  @catch ( NSException *e ) {
 
+
    errorCallback(@[e]);
<syntaxhighlight lang="objective-c">
+
   }
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date)
 
{
 
   // التاريخ جاهز للاستخدام الآن
 
 
}
 
}
 +
</syntaxhighlight>ثم يمكنك في شيفرة JavaScript إضافة دالة رد نداء منفصلة لاستجابات الخطأ والنجاح كما يلي:<syntaxhighlight lang="javascript">
 +
const onPress = () => {
 +
  CalendarModule.createCalendarEventCallback(
 +
    'testName',
 +
    'testLocation',
 +
    (error) => {
 +
      console.error(`Error found! ${error}`);
 +
    },
 +
    (eventId) => {
 +
      console.log(`event id ${eventId} returned`);
 +
    }
 +
  );
 +
};
 +
</syntaxhighlight>إذا أردت تمرير كائنات تشبه الأخطاء إلى شيفرة JavaScript، فاستخدم <code>RCTMakeError</code> من <code>[https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.h RCTUtils.h]</code>، الذي يمرّر حاليًا قاموسًا على شكل خطأ إلى JavaScript، ولكن يهدف React Native إلى إنشاء كائنات خطأ Error حقيقية لشيفرة JavaScript تلقائيًا في المستقبل. يمكنك أيضًا توفير وسيط <code>RCTResponseErrorBlock</code> الذي يُستخدَم لدوال رد نداء الأخطاء ويقبل <code>NSError \* object</code> (لاحظ أن نوع الوسيط هذا لن يدعمه نظام TurboModules).
  
 +
=== الوعود Promises ===
 +
يمكن للوحدات الأصيلة أيضًا أن تفي بالوعود promise، وهذا يبسّط شيفرة JavaScript الخاصة بك، خاصةً عند استخدام صيغة <code>async/await</code> في النسخة ES2016. إذا كان المعامل الأخير لدالة الوحدة الأصيلة هو <code>RCTPromiseResolveBlock</code> و <code>RCTPromiseRejectBlock</code>، فإن دالة JS المقابلة لها ستعيد كائن JS هو كائن الوعد Promise.
  
</syntaxhighlight>
+
تبدو إعادة تصميم الشيفرة السابقة بهدف استخدام وعد بدلًا من دوال رد النداء كما يلي:<syntaxhighlight lang="objective-c">
بعد ذلك سيكون الاستدعاء في جهة JavaScript إما:
+
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
<syntaxhighlight lang="javascript">
+
                location:(NSString *)location
CalendarManager.addEvent(
+
                resolver:(RCTPromiseResolveBlock)resolve
  'Birthday Party',
+
                rejecter:(RCTPromiseRejectBlock)reject)
  '4 Privet Drive, Surrey',
 
  date.getTime(),
 
); // passing date as number of milliseconds since Unix epoch
 
  // تمرير التاريخ كعدد الأجزاء من ألف جزء من الثانية منذ زمن
 
  // Unix
 
</syntaxhighlight>
 
أو
 
<syntaxhighlight lang="javascript">
 
CalendarManager.addEvent(
 
  'Birthday Party',
 
  '4 Privet Drive, Surrey',
 
  date.toISOString(),
 
);
 
  // تمرير التاريخ كسلسلة نصية بتنسيق
 
  // ISO-8601
 
</syntaxhighlight>
 
 
 
وستُحوَّل كلا القيمتان بشكل صحيح إلى النوع <code>NSDate</code> الأصيل. من شأن قيمةٍ سيئةٍ، كمصفوفةٍ (<code>Array</code>)، توليد رسالة خطأ صندوق أحمر ("RedBox") مفيدة.
 
 
 
كلّما ازداد تابع <code>CalendarManager.addEvent</code> تعقيدًا، كلّما ازداد عدد المعاملات. وقد يكون بعضها اختياريًّا. في هذه الحالة، من المفضّل تغيير واجهة برمجة التطبيقات قليلاً لقبول قاموس لسمات الأحداث (event attributes) كما يلي:
 
<syntaxhighlight lang="objective-c">
 
#import <React/RCTConvert.h>
 
 
 
RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details)
 
 
{
 
{
  NSString *location = [RCTConvert NSString:details[@"location"]];
+
NSInteger eventId = createCalendarEvent();
   NSDate *time = [RCTConvert NSDate:details[@"time"]];
+
if (eventId) {
   ...
+
    resolve(@[@(eventId)]);
 +
   } else {
 +
    reject(@"event_failure", @"no event id returned", nil);
 +
   }
 
}
 
}
</syntaxhighlight>
 
  
الاستدعاء من لغة JavaScript:
+
</syntaxhighlight>يعيد نظير JavaScript الخاص بهذا التابع وعدًا  (أي كائن ‎‎<code>[[JavaScript/Promise|Promise‎‎]]</code>). هذا يعني أنه يمكنك استخدام الكلمة المفتاحية <code>[[JavaScript/await|await]]</code> ضمن دالة غير متزامنة لاستدعائها وانتظار نتيجتها:<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
+
const onSubmit = async () => {
CalendarManager.addEvent('Birthday Party', {
+
  try {
  location: '4 Privet Drive, Surrey',
+
    const eventId = await CalendarModule.createCalendarEvent(
   time: date.getTime(),
+
      'Party',
   description: '...',
+
      'my house'
});
+
    );
 +
    console.log(`Created a new event with id ${eventId}`);
 +
   } catch (e) {
 +
    console.error(e);
 +
   }
 +
};
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''ملاحظة:''' حول المصفوفات (array) والترابطات (map)
+
=== إرسال الأحداث إلى JavaScript ===
 +
يمكن للوحدات الأصيلة أن تشير إلى أحداث JavaScript دون استدعائها مباشرة، فقد ترغب مثلًا في إرسال إشارة إلى شيفرة JavaScript لتذكيرها بأن حدث تقويم من تطبيق تقويم iOS الأصيل سيحدث قريبًا. الطريقة المفضلة لتنفيذ ذلك هي إنشاء صنف فرعي من <code>RCTEventEmitter</code>، وتطبيق <code>supportedEvents</code> واستدعاء <code>self sendEventWithName</code> كما يلي:
  
لا توفر لغة Objective-C أي ضمانات حول أنواع القيم في هذه الهياكل. قد تتوقع وحدتك الأصيلة مصفوفة من السلاسل النصية، ولكن إذا استدعَتْ JavaScript تابِعَك باستخدام مصفوفة تحتوي على أرقام وسلاسل نصيّة، فستحصل على مصفوفة <code>NSArray</code> تحتوي على مزيج من أعداد <code>NSNumber</code> وسلاسل <code>NSString</code> النّصيّة. بالنسبة للمصفوفات، يوفر <code>RCTConvert</code> بعض المجموعات المنوَّعة (typed collections) التي يمكنك استخدامها في تعريف تابعك، مثل <code>NSStringArray</code> أو <code>UIColorArray</code>. بالنسبة للترابطات، تقع مسؤولية التحقق من أنواع القيم كل على حدة على عاتق المطور عبر استدعاء توابع <code>RCTConvert</code> المساعِدة يدويًا.
+
حدّث صنف الترويسة لاستيراد <code>RCTEventEmitter</code> والصنف الفرعي <code>RCTEventEmitter</code>:<syntaxhighlight lang="objective-c">
 +
// CalendarModule.h
  
==دوال رد النداء==
+
#import <React/RCTBridgeModule.h>
 +
#import <React/RCTEventEmitter.h>
  
'''تحذير:''' هذا القسم في مرحلة تجريبية أكثر من الأقسام الأخرى، إذ لا وجود لمجموعة جيّدة من أفضل الممارسات (best practices) حول دوال رد النداء حتى الآن.
+
@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
 
+
@end
تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات: والذي يتمثّل في دوال رد نداء. والتي تُستخدم في معظم الحالات لتوفير نتيجة استدعاءِ دالةٍ للغة JavaScript.
+
</syntaxhighlight>يمكن أن تشترك شيفرة JavaScript في هذه الأحداث عن طريق إنشاء نسخة جديدة من <code>NativeEventEmitter</code> حول وحدتك الخاصة. ستتلقى تحذيرًا في حالة استهلاك الموارد دون داعٍ عن طريق إرسال حدث عند عدم وجود مستمعين. يمكنك إعادة كتابة <code>startObserving</code> و <code>stopObserving</code> في صنفك الفرعي <code>RCTEventEmitter</code>، لتجنب ذلك التحذير، ولتحسين العبء على الوحدة الخاصة بك (عن طريق إلغاء الاشتراك بالإشعارات الأساسية أو إيقاف مهام الخلفية مؤقتًا على سبيل المثال).<syntaxhighlight lang="objective-c">
 
+
@implementation CalendarManager
<syntaxhighlight lang="objective-c">
 
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
 
 
{
 
{
   NSArray *events = ...
+
   bool hasListeners;
  callback(@[[NSNull null], events]);
 
 
}
 
}
</syntaxhighlight>
 
  
يقبل <code>RCTResponseSenderBlock</code> معاملًا واحدا فقط، وهو مصفوفةٌ من المعاملات لتمريرها إلى دالة رد نداء JavaScript. في هذه الحالة، نستخدم عُرفَ Node في جعل المعامل الأوَّل كائنَ خطأ (عادةً ما يكون القيمة <code>null</code> في حالة عدم وجود خطأ) والباقي هي نتائج الدالة.
+
// ‫ستُستدعَى عند إضافة المستمع الأول لهذه الوحدة.
<syntaxhighlight lang="javascript">
+
-(void)startObserving {
CalendarManager.findEvents((error, events) => {
+
     hasListeners = YES;
  if (error) {
+
     // اضبط أي مستمع أساسي أو مهام خلفية حسب الضرورة
     console.error(error);
+
}
  } else {
 
     this.setState({events: events});
 
  }
 
});
 
</syntaxhighlight>
 
يجب أن تستدعي الوحدةُ الأصيلة دالة رد النداء الخاصة بها مرة واحدة فقط. لا بأس في تخزين دالة رد النداء واستدعائها لاحقًا. غالبًا ما يُستخدَم هذا النمط لتغليف واجهات برمجة تطبيقات iOS التي تتطلب مفوضين (delegates)، انظر [https://github.com/facebook/react-native/blob/master/React/Modules/RCTAlertManager.m <code>RCTAlertManager</code>] كمثال. إذا لم تُستدعَى دالة رد النداء مطلقًا، فسيُسرَّب جزء من الذاكرة. إذا مُرِّرَت كل من دالتي رد النداء <code>onSuccess</code> و <code>onFail</code>، فينبغي استدعاء واحدة منهما فقط.
 
  
إذا كنت ترغب في تمرير كائنات الأخطاء أو ما شبهها (error-like objects) إلى JavaScript، فاستخدم <code>RCTMakeError</code> من [https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.h ‎‎<code>RCTUtils.h</code>‎‎]. في الوقت الحالي، يقوم هذا بتمرير قاموس على شكل خطأ إلى JavaScript، لكننا (أي فريق React Native) نرغب في إنشاء كائنات <code>Error</code> حقيقية للغة JavaScript تلقائيا في المستقبل.
+
// ستُستدعَى عند إزالة آخر مستمع لهذه الوحدة، أو عند إلغاء التخصيص
 +
-(void)stopObserving {
 +
    hasListeners = NO;
 +
    // أزِل المستمعين الأساسيين، وأوقِف مهام الخلفية غير الضرورية
 +
}
  
==الوعود (Promises)==
+
- (void)calendarEventReminderReceived:(NSNotification *)notification
 
 
يمكن للوحدات الأصيلة أيضًا الوفاء بالوعود، ما يمكن أن يبسط شيفرتك، خاصة عند استخدام بنية ‎‎<code>async/await</code>‎‎ الجديدة في نسخة ES2016. عندما تكون المعاملات الأخيرة للتابع الأصيل الموصَل عن طريق الجسر هي <code>RCTPromiseResolveBlock</code> و<code>RCTPromiseRejectBlock</code>، فسيُعيد تابع JavaScript الخاص به والمقابل له كائنَ <code>Promise</code> في JavaScript.
 
 
 
ستبدو [https://wiki.hsoub.com/Refactoring إعادة تصميم الشيفرة] أعلاه لاستخدام وعدٍ بدلاً من دوال رد النداء كما يلي:
 
 
 
<syntaxhighlight lang="objective-c">
 
RCT_REMAP_METHOD(findEvents,
 
                findEventsWithResolver:(RCTPromiseResolveBlock)resolve
 
                rejecter:(RCTPromiseRejectBlock)reject)
 
 
{
 
{
   NSArray *events = ...
+
   NSString *eventName = notification.userInfo[@"name"];
   if (events) {
+
   if (hasListeners) { // أرسل الأحداث عند وجود شخص يستمع فقط
     resolve(events);
+
     [self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
  } else {
 
    NSError *error = ...
 
    reject(@"no_events", @"There were no events", error);
 
 
   }
 
   }
 
}
 
}
</syntaxhighlight>
 
 
يقوم نظير JavaScript الخاص بهذا التابع بإرجاع وعد (أي كائن ‎‎<code>Promise</code>‎‎). هذا يعني أنه يمكنك استخدام الكلمة المفتاحيّة <code>await</code> داخل دالة غير متزامنة (async function) لاستدعائها وانتظار نتيجتها:
 
<syntaxhighlight lang="javascript">
 
async function updateEvents() {
 
  try {
 
    var events = await CalendarManager.findEvents();
 
  
    this.setState({events});
 
  } catch (e) {
 
    console.error(e);
 
  }
 
}
 
 
updateEvents();
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==التسلسل Threading==
+
===استخدام الخيوط Threading===
لا ينبغي أن تَفترِض الوحدة الأصيلة ماهية السلسلة التي يتم استدعاؤها عليها. تستدعي React Native توابع الوحدات الأصيلة في طابور GCD تسلسليّ منفصل، ولكن هذا تفصيل إجراء (implementation detail) وقد يتغير. يسمح التابع ‎‎<code>- (dispatch_queue_t)methodQueue</code>‎‎ للوحدة الأصيلة بتحديد الطابور الذي يجب تشغيل توابعها عليه. على سبيل المثال، إذا كنت تحتاج إلى استخدام واجهة برمجة تطبيقات iOS ذات السلسلة الرئيسية فقط (main-thread-only iOS API)، فيجب تحديد ذلك عبر:
+
لا ينبغي أن تَفترِض الوحدة الأصيلة الخيط الذي تُستدعَى عليه، إلّا عندما توفّر طابور التوابع الخاص بها. حاليًا، إن لم توفّر الوحدة الأصيلة طابور التوابع، فسينشئ React Native طابور GCD منفصل لها ويستدعي توابعها هناك (لكن ذلك هو أحد تفاصيل التطبيق وقد يتغير). إذا أردت صراحةً توفير طابور توابع لوحدة أصيلة، فأعِد كتابة التابع <code>(dispatch_queue_t) methodQueue</code> في الوحدة الأصيلة، فإذا احتجت مثلًا إلى استخدام واجهة برمجة تطبيقات iOS ذات الخيط الرئيسي فقط main-thread-only iOS API، فيجب تحديد ذلك عن طريق:
 
<syntaxhighlight lang="objective-c">
 
<syntaxhighlight lang="objective-c">
 
- (dispatch_queue_t)methodQueue
 
- (dispatch_queue_t)methodQueue
سطر 353: سطر 362:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
وبالمثل، إذا كانت عملية ما قد تستغرق وقتًا طويلاً لإكمالها، فلا يجب إيقاف الوحدة الأصيلة وينبغي أن تتمكّن من تحديد الطابور الخاص بها لتشغيل العمليات عليه. على سبيل المثال، تقوم الوحدة <code>RCTAsyncLocalStorage</code> بإنشاء طابور خاص بها حتى لا يتوقَّف طابور React مُنتظرًا وصولا بطيئا محتملا إلى القرص:
+
وبالمثل، إذا استغرقت عملية وقتًا طويلًا لإكمالها، فيمكن للوحدة الأصيلة تحديد طابورها الخاص لتشغيل العمليات عليه. سيوفّر React Native حاليًا طابورًا منفصلًا للوحدة الأصيلة، ولكنها تفاصيل تطبيق يجب ألّا تعتمد عليها. إن لم توفّر طابور التوابع الخاص بك، فقد تنتهي في المستقبل عمليات وحدتك الأصيلة ذات الوقت الطويل بوقف الاستدعاءات غير المتزامنة التي تُنفَّذ على وحدات أصيلة أخرى غير متعلقة بها. تنشئ الوحدة <code>RCTAsyncLocalStorage</code> هنا على سبيل المثال طابورًا خاصًا بها حتى لا يتوقَّف طابور React مُنتظرًا وصولًا بطيئًا محتملًا إلى القرص:
 
<syntaxhighlight lang="objective-c">
 
<syntaxhighlight lang="objective-c">
 
- (dispatch_queue_t)methodQueue
 
- (dispatch_queue_t)methodQueue
 
{
 
{
  return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
+
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
سيُشارَك طابور <code>methodQueue</code> المُحدَّد من طرف جميع التوابع في وحدتك. إذا كان تابع واحد فقط من توابعك طويلَ الأمد (أو يحتاج إلى تشغيله في طابور بمعزل عن التوابع الأخرى لسبب ما)، فيمكنك استخدام <code>dispatch_async</code> داخل التابع لتشغيل شيفرة هذا التابع بالذات على طابور آخر، دون التأثير على الطوابير الأخرى:
+
سيتشارك جميع التوابع في وحدتك بالتابع <code>methodQueue</code> المُحدَّد. إذا كان تابع واحد فقط من توابعك ذو وقت تشغيل طويل (أو يحتاج إلى تشغيله في طابور بمعزل عن التوابع الأخرى لسبب ما)، فيمكنك استخدام <code>dispatch_async</code> داخل التابع لتشغيل شيفرة هذا التابع على طابور آخر، دون التأثير على الطوابير الأخرى كما يلي:
 
<syntaxhighlight lang="objective-c">
 
<syntaxhighlight lang="objective-c">
 
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
 
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
 
{
 
{
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     // استدعِ شيفرة طويلة التشغيل في سلسلةٍ خلفيّة
+
     // استدعِ شيفرة ذات وقت تشغيل طويل على خيط خلفيّة
 
     ...
 
     ...
     // يُمكنك استدعاء دالة رد النداء من أي طابور أو سلسلة
+
     // يُمكنك استدعاء دالة رد النداء من أي طابور أو خيط
 
     callback(@[...]);
 
     callback(@[...]);
  });
+
});
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><blockquote>'''مشاركة طوابير الإرسال dispatch queues بين الوحدات:'''
'''ملاحظة''': مشاركة طوابير البعث (dispatch queues) بين الوحدات
 
 
 
سيُستدعَى التابع <code>methodQueue</code> مرة واحدة عند تهيئة الوحدة، ثم سيُحتفَظ به بواسطة الجسر، لذلك لا حاجة للاحتفاظ بالطابور بنفسك، إلا إن أردت الاستفادة منه داخل وحدتك. ومع ذلك، إذا أردت مشاركة الطابور نفسه بين وحدات متعددة، فستحتاج إلى التأكد من أنك تحتفظ بنفس نسخة الطابور (queue instance) وتعيدها لكل منها؛ إذ لن تنجح مجرد إعادة الطابور بنفس الاسم لكل منها.
 
 
 
==حقن الاعتماديات (Dependency Injection)==
 
يُهيّئ الجسر تلقائيًّا الوحدات <code>RCTBridgeModules</code> المسجلة، لكن قد ترغب في تهيئة نسخ وحدات خاصّة بك (لحقن الاعتماديات مثلًا).
 
  
يمكنك ذلك عبر إنشاء صنف يُجرِي (أو يعتمد على) بروتوكول <code>RCTBridgeDelegate</code>، وتهيئة <code>RCTBridge</code> مع المفوَّض كمعامل وتهيئة <code>RCTRootView</code> باستخدام الجسر المُهيَّأ.
+
سيُستدعَى التابع <code>methodQueue</code> مرة واحدة عند تهيئة الوحدة، ثم سيحتفظ به React Native، لذلك لا حاجة للاحتفاظ بمرجع إلى الطابور بنفسك، إلا إن أردت الاستفادة منه داخل وحدتك. لكن إذا أردت مشاركة الطابور نفسه بين وحدات متعددة، فستحتاج إلى التأكد من أنك تحتفظ وتعيد نسخة الطابور نفسها لكل وحدة.</blockquote>
  
<syntaxhighlight lang="objective-c">
+
=== حقن الاعتماديات Dependency Injection ===
 +
سينشئ ويهيّئ React Native أي وحدات أصيلة مُسجَّلة تلقائيًا، ولكن قد ترغب في إنشاء وتهيئة نسخ من وحدتك الخاصة لحقن الاعتماديات على سبيل المثال. يمكنك تطبيق ذلك عن طريق إنشاء صنف يطبّق بروتوكول <code>RCTBridgeDelegate</code> وتهيئة <code>RCTBridge</code> باستخدام المفوَّض كوسيط وتهيئة <code>RCTRootView</code> باستخدام الجسر المهيَّأ.<syntaxhighlight lang="objective-c">
 
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];
 
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];
  
سطر 393: سطر 397:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==تصدير الثوابت (Exporting Constants)==
+
===تصدير Swift===
يمكن لوحدة أصيلة تصدير الثوابت المتاحة حينًا (immediately available) إلى لغة JavaScript في وقت التشغيل (runtime). هذا مفيد لنقل البيانات الساكنة (static data) التي ستتطلب رحلة ذهاب وإياب عبر الجسر في غير ذلك من حالات.
+
لا تدعم لغة Swift الماكرو macros، لذلك يتطلب إظهار الوحدات الأصيلة وتوابعها لشيفرة JavaScript داخل React Native إعدادًا إضافيًّا، ولكنه يعمل نسبيًا بنفس الطريقة. لِنقُل أن لدينا نفس الصنف <code>CalendarManager</code> ولكن كصنفٍ في لغة Swift:<syntaxhighlight lang="swift">
<syntaxhighlight lang="objective-c">
 
- (NSDictionary *)constantsToExport
 
{
 
  return @{ @"firstDayOfTheWeek": @"Monday" };
 
}
 
</syntaxhighlight>
 
 
 
يمكن للغة JavaScript  استخدام هذه القيمة على الفور بشكل متزامن:
 
<syntaxhighlight lang="javascript">
 
console.log(CalendarManager.firstDayOfTheWeek);
 
</syntaxhighlight>
 
لاحظ أن الثوابت تُصدَّر فقط في وقت التهيئة، لذلك إذا غيرت قيم <code>constantsToExport</code> في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript.
 
==إجراء ‎‎<code>+ requiresMainQueueSetup</code>‎‎==
 
إذا تجاوزت (أو أبطلت [override]) ‎‎<code>- constantsToExport</code>‎‎، فعليك أيضًا إجراء ‎‎<code>+ requiresMainQueueSetup</code>‎‎ للسماح لإطار React Native بمعرفة ما إذا كانت وحدتك بحاجة إلى تهيئتها في السلسلة الرئيسية. وإلا سترى تحذيرًا بأنه في المستقبل قد تتم تهيئة وحدتك على سلسلة في الخلفية ما لم تكن قد قمت صراحةً بإلغاء الاشتراك مع ‎‎<code>+ requiresMainQueueSetup</code>‎‎:
 
<syntaxhighlight lang="objective-c">
 
+ (BOOL)requiresMainQueueSetup
 
{
 
  // استعمل هذا فقط في حالة كانت تهيئة وحدتك تعتمد على استدعاء
 
  // UIKit!
 
  return YES;
 
}
 
</syntaxhighlight>
 
 
 
إذا كانت وحدتك لا تتطلب الوصول إلى UIKit، فيجب عليك الرد على ‎‎<code>+ requiresMainQueueSetup</code>‎‎ بالقيمة <code>NO</code>.
 
 
 
==الثوابت المعددة (Enum Constants)==
 
لا يمكن استخدام الثوابت المُعدَّدَة المعرَّفة من خلال <code>NS_ENUM</code> كمعاملاتِ توابعٍ (method arguments) دون توسيع <code>RCTConvert</code> أولاً.
 
 
 
لتصدير تعريف <code>NS_ENUM</code> التالي:
 
<syntaxhighlight lang="objective-c">
 
typedef NS_ENUM(NSInteger, UIStatusBarAnimation) {
 
    UIStatusBarAnimationNone,
 
    UIStatusBarAnimationFade,
 
    UIStatusBarAnimationSlide,
 
};
 
</syntaxhighlight>
 
يجب عليك إنشاء توسِعة صنفٍ (class extension) للصنف <code>RCTConvert</code> هكذا:
 
<syntaxhighlight lang="objective-c">
 
@implementation RCTConvert (StatusBarAnimation)
 
  RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),
 
                                              @"statusBarAnimationFade" : @(UIStatusBarAnimationFade),
 
                                              @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)}),
 
                      UIStatusBarAnimationNone, integerValue)
 
@end
 
</syntaxhighlight>
 
يمكنك بعد ذلك تحديد التوابع وتصدير الثوابت المعدّدة الخاصة بك هكذا:
 
<syntaxhighlight lang="objective-c">
 
- (NSDictionary *)constantsToExport
 
{
 
  return @{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),
 
            @"statusBarAnimationFade" : @(UIStatusBarAnimationFade),
 
            @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide) };
 
};
 
 
 
RCT_EXPORT_METHOD(updateStatusBarAnimation:(UIStatusBarAnimation)animation
 
                                completion:(RCTResponseSenderBlock)callback)
 
</syntaxhighlight>
 
سيُلغى بعد ذلك تغليف ثابتك المُعدَّد تلقائيًا باستخدام المُحدِّد المُوفَّر (<code>integerValue</code> في المثال أعلاه) قبل تمريره إلى التابع المُصدَّر.
 
 
 
==إرسال الأحداث إلى JavaScript==
 
يمكن للوحدة الأصيلة أن تشير إلى الأحداث (signal events) إلى JavaScript دون استدعائها مباشرة. الطريقة المفضلة للقيام بذلك هي إنشاء صنف فرعي من <code>RCTEventEmitter</code>، وإجراء <code>supportedEvents</code> واستدعاء ‎‎<code>self sendEventWithName</code>‎‎:
 
<syntaxhighlight lang="objective-c">
 
// CalendarManager.h
 
#import <React/RCTBridgeModule.h>
 
#import <React/RCTEventEmitter.h>
 
 
 
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
 
 
 
@end
 
</syntaxhighlight>
 
 
 
<syntaxhighlight lang="objective-c">
 
// CalendarManager.m
 
#import "CalendarManager.h"
 
 
 
@implementation CalendarManager
 
 
 
RCT_EXPORT_MODULE();
 
 
 
- (NSArray<NSString *> *)supportedEvents
 
{
 
  return @[@"EventReminder"];
 
}
 
 
 
- (void)calendarEventReminderReceived:(NSNotification *)notification
 
{
 
  NSString *eventName = notification.userInfo[@"name"];
 
  [self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
 
}
 
 
 
@end
 
</syntaxhighlight>
 
يمكن لشيفرة JavaScript الاشتراك (subscribe) في هذه الأحداث من خلال إنشاء نسخة <code>NativeEventEmitter</code> جديدة حول وحدتك.
 
<syntaxhighlight lang="javascript">
 
import { NativeEventEmitter, NativeModules } from 'react-native';
 
const { CalendarManager } = NativeModules;
 
 
 
const calendarManagerEmitter = new NativeEventEmitter(CalendarManager);
 
 
 
const subscription = calendarManagerEmitter.addListener(
 
  'EventReminder',
 
  (reminder) => console.log(reminder.name)
 
);
 
...
 
// لا تنس إلغاء الاشتراك، الذي يُؤدَّى عادةً في
 
// componentWillUnmount
 
subscription.remove();
 
</syntaxhighlight>
 
 
 
لمزيد من أمثلة إرسال الأحداث إلى JavaScript، راجع [https://github.com/facebook/react-native/blob/master/Libraries/Geolocation/RCTLocationObserver.m <code>RCTLocationObserver</code>].
 
==تحسين الأداء عند انعدام المستمعين (Optimizing for zero listeners)==
 
ستتلقى تحذيرًا إذا كنت تستعمل الموارد بلا حاجة عن طريق إرسال حدث أثناء عدم وجود مستمعين. لتجنب هذا الأمر، ولتحسين عبء العمل على وحدتك (على سبيل المثال عن طريق إلغاء الاشتراك من التنبيهات الأولية أو إيقاف مهام الخلفية مؤقتًا)، يمكنك تجاوز <code>startObserving</code> و<code>stopObserving</code> في الصنف الفرعي <code>RCTEventEmitter</code> الخاص بك.
 
<syntaxhighlight lang="objective-c">
 
@implementation CalendarManager
 
{
 
  bool hasListeners;
 
}
 
 
 
// سيُستدعى عندما يُضاف أول مستمع إلى هذه الوحدة
 
-(void)startObserving {
 
    hasListeners = YES;
 
    // اضبِط مستمعين أو مهام خلفية حسب الحاجة
 
}
 
 
 
 
 
// سيُستدعى عندما يُزال آخر مستمع إلى هذه الوحدة، أو إذا كان في حالة
 
// dealloc
 
-(void)stopObserving {
 
    hasListeners = NO;
 
    // أزِل المستمعين، وأوقف مهام الخلفية غير الضرورية
 
}
 
 
 
- (void)calendarEventReminderReceived:(NSNotification *)notification
 
{
 
  NSString *eventName = notification.userInfo[@"name"];
 
  if (hasListeners) { // أرسل الأحداث فقط إذا كان هناك مستمع
 
    [self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
 
  }
 
}
 
</syntaxhighlight>
 
==تصدير Swift==
 
دعم الماكرو غير موجود في لغة Swift، لذا فإن توفير الوصول إليها لإطار React Native يتطلب إعدادًا إضافيًّا، ولكنه يعمل نسبيًا بنفس الشكل.
 
 
 
لِنقُل أن لدينا نفس الصنف <code>CalendarManager</code> ولكن كصنفٍ في لغة Swift:
 
<syntaxhighlight lang="swift">
 
 
// CalendarManager.swift
 
// CalendarManager.swift
  
سطر 545: سطر 404:
 
class CalendarManager: NSObject {
 
class CalendarManager: NSObject {
  
  @objc(addEvent:location:date:)
+
@objc(addEvent:location:date:)
  func addEvent(name: String, location: String, date: NSNumber) -> Void {
+
func addEvent(_ name: String, location: String, date: NSNumber) -> Void {
    // التاريخ جاهز للاستخدام
+
  // ‫التاريخ Date جاهز للاستخدام
  }
+
}
  
  @objc
+
@objc
  func constantsToExport() -> [String: Any]! {
+
func constantsToExport() -> [String: Any]! {
    return ["someKey": "someValue"]
+
  return ["someKey": "someValue"]
  }
+
}
  
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><blockquote>يجب استخدام المُعدِّلات <code>objc@</code> لضمان تصدير الصنف والدّوال تصديرًا صحيحًا إلى وقت تشغيل Objective-C.</blockquote>ثم أنشِأ ملف تنفيذ خاص ليُسَجِّل المعلومات المطلوبة من React Native:
 
+
<syntaxhighlight lang="swift">
'''ملاحظة:''' من المهم استخدام المُعدِّل <code>objc@</code> لضمان تصدير الصنف والدّوال بشكل صحيح إلى وقت تشغيل Objective-C.
 
 
 
ثم أنشِأ ملف إجراءٍ خاص (private implementation file) ليُسَجِّل المعلومات المطلوبة مع جسر React Native:
 
<syntaxhighlight lang="objective-c">
 
 
// CalendarManagerBridge.m
 
// CalendarManagerBridge.m
 
#import <React/RCTBridgeModule.h>
 
#import <React/RCTBridgeModule.h>
سطر 572: سطر 427:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''بالنسبة للمستجدّين على Swift و Objective-C:''' كلما قمت [https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html بخلط اللغتين في مشروع iOS]، ستحتاج كذلك إلى ملف تجسير (bridging file) إضافيّ يُعرَف بترويسة التجسير (bridging header)، لتوفير الوصول إلى ملفات Objective-C من Swift. سيقترح Xcode إنشاء ملف الترويسة هذا لك إذا أضفت ملف Swift إلى تطبيقك من خلال خيار القائمة ‎‎<code>File>New File</code>‎‎. ستحتاج إلى استيراد <code>RCTBridgeModule.h</code> في ملف الترويسة هذا.
+
بالنسبة للأشخاص الجديدين على لغتَي Swift و Objective-كلما [https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html مزجت اللغتين في مشروع iOS]، ستحتاج إلى ملف تجسير bridging file إضافيّ يُعرَف بترويسة التجسير bridging header، لتوفير الوصول إلى ملفات Objective-C من Swift. سيقترح Xcode إنشاء ملف الترويسة هذا لك إذا أضفت ملف Swift إلى تطبيقك من خلال خيار القائمة ملف ‎‎<code>File>New File</code>‎‎. ستحتاج إلى استيراد <code>RCTBridgeModule.h</code> في ملف الترويسة هذا.
<syntaxhighlight lang="objective-c">
+
<syntaxhighlight lang="swift">
 
// CalendarManager-Bridging-Header.h
 
// CalendarManager-Bridging-Header.h
 
#import <React/RCTBridgeModule.h>
 
#import <React/RCTBridgeModule.h>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
يمكنك أيضًا استخدام <code>RCT_EXTERN_REMAP_MODULE</code> و‎‎<code>_RCT_EXTERN_REMAP_METHOD</code>‎‎ لتغيير اسم JavaScript للوحدة أو التوابع التي تُصدِّرها. لمزيد من المعلومات، راجع [https://github.com/facebook/react-native/blob/master/React/Base/RCTBridgeModule.h RCTBridgeModule].
+
يمكنك أيضًا استخدام <code>RCT_EXTERN_REMAP_MODULE</code> و‎‎<code>_RCT_EXTERN_REMAP_METHOD</code>‎‎ لتغيير اسم JavaScript للوحدة أو التوابع التي تُصدِّرها. اطّلع على [https://github.com/facebook/react-native/blob/master/React/Base/RCTBridgeModule.h <code>RCTBridgeModule</code>] لمزيد من المعلومات.<blockquote>'''ملاحظة مهمّة عند إنشاء وحدات تابعة لطرف ثالث third party modules''':
 +
 
 +
المكتبات الساكنة في Swift مدعومة فقط في الإصدار Xcode 9 والإصدارات الأحدث. يجب أن يحتوي مشروع تطبيقك الرئيسي على شيفرة Swift وترويسة التجسير نفسها، لكي يُبنَى مشروع Xcode عند استخدام لغة Swift في مكتبة iOS الساكنة التي تضمّنها في الوحدة. إن لم يتضمّن مشروع تطبيقك أي شيفرة Swift، فيمكن حل المشكلة بملف ‎‎.swift فارغٍ وترويسة تجسير فارغة.</blockquote>
 +
 
 +
=== أسماء التوابع المحجوزة ===
  
'''ملاحظة مهمّة عند إنشاء وحدات تابعة لطرف ثالث (third party modules)''': المكتبات الساكنة في Swift مدعومة فقط في Xcode 9 والإصدارات الأحدث. لكي يُبنَى مشروع Xcode عند استخدام Swift في مكتبة iOS الساكنة التي تدرجها في الوحدة، يجب أن يحتوي مشروع تطبيقك الرئيسي على شيفرة Swift وترويسة التجسير نفسها. إذا كان مشروع تطبيقك لا يحتوي على أي شيفرة Swift، فيمكن حل المشكلة بملف ‎‎<code>.swift</code>‎‎ فارغٍ وترويسة تجسير فارغة.
+
==== invalidate()‎ ====
 +
يمكن للوحدات الأصيلة أن تتوافق مع بروتوكول [https://github.com/facebook/react-native/blob/0.62-stable/React/Base/RCTInvalidating.h RCTInvalidating] على نظام iOS من خلال تطبيق التابع <code>invalidate()‎</code>. يمكن [https://github.com/facebook/react-native/blob/0.62-stable/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm#L456 استدعاء هذا التابع] عند إلغاء الجسر الأصيل (عند إعادة تحميل وضع التطوير devmode على سبيل المثال). يرجى استخدام هذه الآلية حسب الضرورة لإجراء التنظيف المطلوب لوحدتك الأصيلة.
  
 
== مصادر ==
 
== مصادر ==
* [https://facebook.github.io/react-native/docs/native-modules-ios صفحة Native Modules في توثيق React Native الرسمي.]
+
* [https://reactnative.dev/docs/native-modules-ios صفحة iOS Native Modules في توثيق React Native الرسمي.]
 
[[تصنيف:ReactNative]]
 
[[تصنيف:ReactNative]]
 +
[[تصنيف:React Native Docs]]

المراجعة الحالية بتاريخ 13:49، 9 أكتوبر 2021

يُرجى الاطّلاع أولًا على صفحة مدخل إلى الوحدات الأصيلة Native Modules للتعرّف على الوحدات الأصيلة.

إنشاء وحدة تقويم أصيلة كمثال Calendar Native Module

سننشئ وحدة أصيلة هي الوحدة CalendarModule التي ستسمح بالوصول إلى واجهات برمجة تقويم Apple البرمجية من شيفرة JavaScript، وستتمكّن في النهاية من استدعاء التابع CalendarModule.createCalendarEvent('Dinner Party', 'My House');‎ من JavaScript ، أي ستستدعي تابعًا أصيلًا ينشئ حدث التقويم.

يعمل فريق React Native حاليًا على إعادة بناء نظام الوحدات الأصيلة، ويُطلَق على هذا النظام الجديد اسم TurboModules الذي سيساعد في تسهيل إنشاء اتصال أكثر كفاءة ومن النوع الآمن بين شيفرة JavaScript والشيفرة الأصيلة، دون الاعتماد على جسر React Native، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من هنا). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.

الإعداد

افتح أولًا مشروع iOS داخل تطبيق React Native الخاص بك في Xcode. يمكنك العثور على مشروع iOS الخاص بك داخل تطبيق React Native كما في الشكل التالي:

native modules ios open project

نوصيك باستخدام Xcode لكتابة شيفرتك الأصيلة، إذ بُنِي Xcode لتطوير تطبيقات iOS، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.

إنشاء ملفات الوحدة الأصيلة المخصصة

تتمثل الخطوة الأولى في إنشاء ترويسة الوحدة الأصيلة المخصَّصة وملفات التنفيذ. أنشئ ملفًا جديدًا بالاسم RCTCalendarModule.h كما يلي:

native modules ios add class
إنشاء ملف وحدة أصيلة مخصصَّة ضمن نفس مجلد AppDelegate

وأضِف ما يلي إلى هذا الملف:

//  RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end

يمكنك استخدام أيّ اسم يناسب الوحدة الأصيلة التي تنشئها، إذ يمكنك تسمية الصنف RCTCalendarModule بما أنك تنشئ وحدة تقويم أصيلة. لا تحتوي لغة ObjC دعمًا على مستوى اللغة لفضاءات الأسماء مثل لغتي Java أوC++ ‎، لذلك يجب أن تسبق سلسلةٌ نصية فرعية اسمَ الصنف، وقد تكون هذه السلسلة النصية اختصارًا لاسم تطبيقك أو لاسم بنيته التحتية، إذ تشير RCT في هذا المثال إلى React.

يطبّق الصنفُ CalendarModule بروتوكولَ RCTBridgeModule كما سترى، فالوحدة الأصيلة هي صنف Objective-C يطبّق بروتوكول RCTBridgeModule.

لنبدأ بعد ذلك في تطبيق الوحدة الأصيلة. أنشئ ملف التنفيذ المقابل RCTCalendarModule.m في نفس المجلد وضمِّن ما يلي:

// RCTCalendarModule.m
#import "RCTCalendarModule.h"

@implementation RCTCalendarModule

// ‫لتصدير وحدة بالاسم RCTCalendarModule
RCT_EXPORT_MODULE();

@end

اسم الوحدة

تشتمل حاليًا الوحدة الأصيلة RCTCalendarModule.m فقط على الماكرو RCT_EXPORT_MODULE والذي يصدّر ويسجّل صنف الوحدة الأصيلة باستخدام React Native. يأخذ الماكرو RCT_EXPORT_MODULE أيضًا وسيطًا اختياريًا يحدّد الاسم الذي يمكن الوصول من خلاله إلى الوحدة كما في شيفرة JavaScript الخاصة بك.

ليس هذا الوسيط قيمة حرفية للسلسلة النصية، إذ مُرِّر اسم الوحدة بهذا الشكل RCT_EXPORT_MODULE (CalendarModuleFoo) وليس RCT_EXPORT_MODULE("CalendarModuleFoo") في المثال التالي:

// ‫لتصدير وحدة بالاسم CalendarModuleFoo
RCT_EXPORT_MODULE(CalendarModuleFoo);

يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JS كما يلي:

const { CalendarModuleFoo } = ReactNative.NativeModules;

إذا لم تحدد اسمًا، فسوف يتطابق اسم وحدة JavaScript مع اسم صنف Objective-C، مع إزالة البادئات مثل "RCT" أو "RK". سنستدعي الآن RCT_EXPORT_MODULE دون وسطاء. ستظهر الوحدة لإطار عمل React Native بالاسم CalendarModule، بما أنه اسم صنف Objective-C، ولكن دون RCT.

// ‫سيؤدي عدم تمرير الاسم إلى تصدير اسم الوحدة الأصيلة كاسم صنف Objective-C دون "RCT"
RCT_EXPORT_MODULE();

ثم يمكن الوصول إلى الوحدة الأصيلة في شيفرة JS كما يلي:

const { CalendarModule } = ReactNative.NativeModules;

تصدير تابع أصيل إلى شيفرة JavaScript

لن يعرض React Native أي عمليات في الوحدة الأصيلة لشيفرة JavaScript ما لم يُطلَب منه ذلك صراحةً، ويمكن ذلك باستخدام الماكرو RCT_EXPORT_METHOD. التوابع المكتوبة في الماكرو RCT_EXPORT_METHOD غير متزامنة، وبالتالي يكون نوع القيمة المُعادة دائمًا void. يمكن تمرير نتيجة من تابع الماكرو RCT_EXPORT_METHOD إلى شيفرة JavaScript باستخدام توابع رد النداء callbacks أو إرسال الأحداث (التي سنتحدث عنها لاحقًا). لنبدأ الآن بإعداد تابع أصيل للوحدة الأصيلة CalendarModule باستخدام الماكرو RCT_EXPORT_METHOD. أطلِق على هذا التابع الاسم createCalendarEvent()‎ واجعله يأخذ حاليًا وسيطَي الاسم name والموقع location كسلاسل نصية (سنتكلم عن خيارات نوع الوسيط لاحقًا أيضًا).

RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}

ملاحظة: لن يكون الماكرو RCT_EXPORT_METHOD ضروريًا مع نظام TurboModules إلّا إن اعتمد تابعك على تحويل وسيط RCT (اطّلع على أنواع الوسطاء أدناه). سيزيل React Native في النهاية RCT_EXPORT_MACRO، لذلك لا نشجّعك على استخدام RCTConvert، بل يمكنك إجراء تحويل الوسيط ضمن جسم التابع.

أضِف سجل وحدة التحكم console log في التابع قبل بناء وظائف التابع createCalendarEvent()‎، لتتمكّن من تأكيد أن التابع اُستدعِي من شيفرة JavaScript في تطبيق React Native الخاص بك. استخدم واجهات الترويسة RCTLog البرمجية من React. استورد هذه الترويسة في أعلى ملفك ثم أضِف استدعاء السجل كما يلي:

#import <React/RCTLog.h>
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
 RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}

التوابع المتزامنة

يمكنك استخدام RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD لإنشاء تابع أصيل متزامن كما يلي:

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}

يجب أن يكون نوع القيمة المُعادة لهذا التابع من نوع الكائن (معرّف id) ويجب أن يكون قابلًا للتسلسل إلى JSON، أي أن الخطّاف hook يمكنه فقط أن يعيد قيم nil أو JSON (مثل NSNumber و NSString و NSArray و NSDictionary).

لا نوصي حاليًا باستخدام توابع متزامنة، لأن استدعاء التوابع المتزامن يمكن أن يكون له عواقب على الأداء ويمكن أن يدخل أخطاءً مرتبطة بالخيوط إلى وحداتك الأصيلة. لاحظ أيضًا أنه إذا اخترت استخدام RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD، فلن يتمكن تطبيقك من استخدام منقّح أخطاء Google Chrome مرة أخرى، لأن التوابع المتزامنة تتطلب آلة JS الافتراضية لمشاركة الذاكرة مع التطبيق. بينما بالنسبة لمنقّح أخطاء Google Chrome، فإن React Native يعمل داخل آلة JS الافتراضية في Google Chrome، ويتواصل تواصلًا غير متزامن مع الأجهزة المتنقلة عبر WebSockets.

اختبر ما بنيته

أجريت حتى الآن الإعداد الأساسي لوحدتك الأصيلة في iOS. اختبر هذا الإعداد من خلال الوصول إلى الوحدة الأصيلة واستدعاء تابع تصديرها في شيفرة JavaScript.

ابحث عن مكانٍ ما في تطبيقك حيث تريد إضافة استدعاء تابع الوحدة الأصيلة createCalendarEvent()‎. يحتوي المثال التالي المكوّن NewModuleButton الذي يمكنك إضافته إلى تطبيقك، حيث يمكنك استدعاء الوحدة الأصيلة ضمن الدالة onPress()‎ الخاصة بالمكوّن NewModuleButton:

import React from 'react';
import { NativeModules, Button } from 'react-native';

const NewModuleButton = () => {
  const onPress = () => {
    console.log('We will invoke the native module here!');
  };

  return (
    <Button
      title="Click to invoke your native module!"
      color="#841584"
      onPress={onPress}
    />
  );
};

export default NewModuleButton;

يجب استيراد NativeModules من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة JavaScript:

import { NativeModules } from 'react-native';

ثم يمكنك الوصول إلى الوحدة الأصيلة CalendarModule من خارج NativeModules:

const { CalendarModule } = NativeModules;

يمكنك الآن استدعاء التابع الأصيل createCalendarEvent()‎ بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى تابع onPress()‎ في المكوّن NewModuleButton:

const onPress = () => {
  CalendarModule.createCalendarEvent('testName', 'testLocation');
};

الخطوة الأخيرة هي إعادة بناء تطبيق React Native بحيث يمكنك الحصول على أحدث شيفرة أصيلة (مع الوحدة الأصيلة الجديدة). شغّل الأمر التالي في سطر الأوامر ضمن المكان الذي يوجد فيه تطبيق react native:

npx react-native run-ios

إعادة البناء عند التكرار

ستحتاج أثناء تكرار وحدتك الأصيلة إلى إعادة بناء أصيلة لتطبيقك بهدف الوصول إلى أحدث التغييرات من شيفرة JavaScript، لأن الشيفرة التي تكتبها موجودة ضمن الجزء الأصيل من تطبيقك. يمكن لمجمّع metro الخاص بإطار عمل React Native مراقبة التغييرات في شيفرة JavaScript وإعادة إنشاء حزمة JS سريعًا نيابةً عنك، إلّا أنه لن يفعل ذلك مع الشيفرة الأصيلة. لذلك إذا أردت اختبار أحدث التغييرات الأصيلة، فيجب إعادة البناء باستخدام الأمر npx react-native run-ios.

الخلاصة

يجب أن تكون الآن قادرًا على استدعاء التابع createCalendarEvent()‎ للوحدة الأصيلة في شيفرة JavaScript. بما أنك تستخدم RCTLog، فيمكنك تأكيد استدعاء تابعك الأصيل من خلال تفعيل وضع تنقيح الأخطاء في تطبيقك والنظر إلى وحدة تحكم JS في Chrome أو منقّح أخطاء تطبيقات الهواتف المحمولة Flipper.

يجب أن ترى رسالة RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);‎ في كل مرة تستدعي فيها تابع الوحدة الأصيلة.

native modules ios logs
سجلات iOS في Flipper

أنشأت حتى الآن وحدة iOS أصيلة واستدعيت تابعًا لها من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد توابع رد النداء callbacks والوعود promises ضمن وحدتك الأصيلة.

ما بعد وحدة التقويم الأصيلة

تصدير الوحدة الأصيلة الأفضل

يُعَد استيراد وحدتك الأصيلة عن طريق سحبها من NativeModules كما ذكرنا سابقًا أمرًا صعبًا بعض الشيء. يمكنك إنشاء مغلِّف JavaScript للوحدة، للحفاظ على مستهلكي وحدتك الأصيلة الخاصة من الحاجة إلى تنفيذ هذا الأمر الصعب في كل مرة يريدون فيها الوصول إلى وحدتك الأصيلة. أنشئ ملف JavaScript جديد باسم NativeCalendarModule.js يحتوي على ما يلي:

/**
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:

* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export default CalendarModule;

يصبح ملف JavaScript هذا أيضًا موقعًا جيدًا لإضافة أي عمليات من جانب شيفرة JavaScript. إذا استخدمت مثلًا نظام أنواع مثل TypeScript، فيمكنك إضافة تعليقات الأنواع للوحدة الأصيلة في هذا الملف. لا يدعم React Native حتى الآن أمان النوع Native to JS، ولكنّ جميع شيفرات JS الخاصة بك ستكون من النوع الآمن. ستسهّل هذه التعليقات عليك التبديل إلى الوحدات الأصيلة ذات النوع الآمن باستمرار. يوضّح المثال التالي إضافة النوع الآمن إلى وحدة التقويم Calendar Module:

/**
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:
*
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules
interface CalendarInterface {
   createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

يمكنك في ملفات JavaScript الأخرى الوصول إلى الوحدة الأصيلة واستدعاء تابعها كما يلي:

import NativeCalendarModule from './NativeCalendarModule';
NativeCalendarModule.createCalendarEvent('foo', 'bar');

لاحظ أن هذا المثال افترض أن المكان الذي تستورده منه CalendarModule موجود ضمن تسلسل الملف CalendarModule.js الهرمي نفسه، لذلك يجب أن تحدّث مسار الاستيراد النسبي عند الحاجة.

أنواع الوسطاء

يحوّل React Native الوسطاء من كائنات JS إلى مثيلاتها في لغة Objective-C أو Swift عند استدعاء تابع الوحدة الأصلية في شيفرة JavaScript . إذا قَبِل تابع وحدة Objective-C الأصيلة وسيطًا من النوع NSNumber على سبيل المثال، فستحتاج في JS إلى استدعاء التابع باستخدام وسيط من النوع عدد number، إذ سيتوّلى React Native عملية التحويل نيابةً عنك. فيما يلي قائمة بأنواع الوسطاء المدعومة لتوابع الوحدات الأصيلة ومقابلاتها في JavaScript التي ستُربَط معها:

OBJECTIVE-C JAVASCRIPT
NSString string, ?string سلسلة نصية
BOOL boolean قيمة منطقية
NSNumber ‎?boolean قيمة منطقية
double number عدد
NSNumber ‎?number عدد
NSArray Array, ?Array مصفوفة
NSDictionary Object, ?Object كائن
RCTResponseSenderBlock Function (success) دالة (نجاح)
RCTResponseSenderBlock, RCTResponseErrorBlock Function (failure) دالة (فشل)
RCTPromiseResolveBlock, RCTPromiseRejectBlock Promise وعد

الأنواع التالية مدعومة حاليًا ولكن لن يدعمها نظام TurboModules، لذلك يُرجَى تجنب استخدامها:

  • Function (failure) -> RCTResponseErrorBlock دالة (فشل)
  • Number -> NSInteger عدد
  • Number -> CGFloat عدد
  • Number -> float عدد

يمكنك أيضًا في نظام التشغيل iOS كتابة توابع الوحدة الأصيلة باستخدام أي نوع وسيط يدعمه الصنف RCTConvert (اطّلع على RCTConvert للحصول على تفاصيل حول ما يدعمه هذا الصنف). تقبل جميع دوال مساعد RCTConvert قيمة JSON كدخل وتربطه مع صنف أو نوع Objective-C أصيل.

تصدير الثوابت

يمكن للوحدة الأصيلة تصدير الثوابت عن طريق إعادة كتابة التابع الأصيل constantsToExport()‎. سيُعاد كتابة التابع constantsToExport()‎ الذي يعيد قاموسًا Dictionary يحتوي على خاصية اسم الحدث الافتراضي الذي يمكنك الوصول إليه في شيفرة JavaScript كما يلي:

- (NSDictionary *)constantsToExport
{
 return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}

يمكن بعد ذلك الوصول إلى الثابت عن طريق استدعاء getConstants()‎ ضمن الوحدة الأصيلة في شيفرة JS كما يلي:

const { DEFAULT_EVENT_NAME } = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

يمكن الوصول إلى الثوابت المُصدَّرة في التابع constantsToExport()‎ مباشرةً من الكائن NativeModule، ولكن لن يدعم نظام TurboModules ذلك، لذلك نشجّع المجتمع على التبديل إلى الطريقة السابقة لتجنّب التهجير الضروري باستمرار.

لاحظ أن الثوابت تُصدَّر فقط في وقت التهيئة initialization time، لذلك إذا غيّرت قيم التابع constantsToExport()‎ في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript.

إذا أعدّت كتابة constantsToExport()‎ بالنسبة لنظام iOS، فيجب عليك أيضًا تطبيق ‎+ requiresMainQueueSetup للسماح لإطار عمل React Native بمعرفة ما إذا كانت وحدتك بحاجة إلى التهيئة على الخيط الرئيسي قبل تنفيذ أي شيفرة JavaScript، وإلّا فسترى تحذيرًا من أن وحدتك ستُهيَّأ في المستقبل على خيط في الخلفية إن لم تلغِ الاشتراك صراحةً من ‎+ requiresMainQueueSetup:‎. إن لم تتطلّب وحدتك الخاصة الوصول إلى UIKit، فيجب أن ترد على ‎+ requiresMainQueueSetup بالرد لا NO.

دوال رد النداء Callbacks

تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات الذي يتمثّل في دوال رد النداء Callbacks. تُستخدَم دوال رد النداء لتمرير البيانات من شيفرة Objective-C إلى شيفرة JavaScript للتوابع غير المتزامنة، ويمكن استخدامها أيضًا لتنفيذ شيفرة JS تنفيذًا غير متزامن من الجانب الأصيل.

تُطبَّق دوال رد النداء بالنسبة لنظام iOS باستخدام النوع RCTResponseSenderBlock. فيما يلي أُضيف معامل رد النداء myCallBack إلى التابع createCalendarEventMethod()‎:

RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
                location:(NSString *)location
                myCallback:(RCTResponseSenderBlock)callback)

ثم يمكنك بعد ذلك استدعاء دالة رد النداء في دالتك الأصيلة، مع توفير أي نتيجة تريد تمريرها إلى JavaScript في مصفوفة. لاحظ أن النوع RCTResponseSenderBlock يقبل وسيطًا واحدًا فقط، وهو مصفوفة من المعاملات لتمريرها إلى دالة رد نداء JavaScript. سنمرّر أدناه معرّف ID الحدث الذي أُنشِئ في استدعاء سابق.

يجب التركيز على أن دالة رد النداء لا تُستدعَى فور اكتمال الدالة الأصيلة، لأن الاتصال غير متزامن.

RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
 NSInteger eventId = ...
 callback(@[@(eventId)]);

 RCTLogInfo(@"Pretending to create an event %@ at %@", title, location);
}

يمكن بعد ذلك الوصول إلى هذا التابع في JavaScript كما يلي:

const onSubmit = () => {
  CalendarModule.createCalendarEvent(
    'Party',
    '04-12-2020',
    (eventId) => {
      console.log(`Created a new event with id ${eventId}`);
    }
  );
};

يُفترَض أن تستدعي الوحدة الأصيلة دالة رد النداء الخاصة بها مرة واحدة فقط، ولكن يمكنها تخزين دالة رد النداء واستدعاؤها لاحقًا. يُستخدَم هذا النمط غالبًا لتغليف واجهات iOS البرمجية التي تتطلب مفوضين delegates (اطّلع على RCTAlertManager كمثال). إذا لم تُستدعَى دالة رد النداء مطلقًا، فسيُسرَّب جزء من الذاكرة. هناك طريقتان لمعالجة أخطاء دوال رد النداء، فالطريقة الأولى هي اتباع عُرف Node ومعاملة الوسيط الأول الذي يُمرَّر إلى مصفوفة رد النداء ككائن خطأ.

RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
  NSNumber *eventId = [NSNumber numberWithInt:123];
  callback(@[[NSNull null], eventId]);
}

ثم يمكنك في شيفرة JavaScript التحقق من أن الوسيط الأول قد مرّر خطأً أم لا كما يلي:

const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error, eventId) => {
      if (error) {
        console.error(`Error found! ${error}`);
      }
      console.log(`event id ${eventId} returned`);
    }
  );
};

أما الطريقة الثانية هي استخدام دالتي رد نداء منفصلتين هما: onFailure و onSuccess.

RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
                  location:(NSString *)location
                  errorCallback: (RCTResponseSenderBlock)errorCallback
                  successCallback: (RCTResponseSenderBlock)successCallback)
{
  @try {
    NSNumber *eventId = [NSNumber numberWithInt:123];
    successCallback(@[eventId]);
  }

  @catch ( NSException *e ) {
    errorCallback(@[e]);
  }
}

ثم يمكنك في شيفرة JavaScript إضافة دالة رد نداء منفصلة لاستجابات الخطأ والنجاح كما يلي:

const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error) => {
      console.error(`Error found! ${error}`);
    },
    (eventId) => {
      console.log(`event id ${eventId} returned`);
    }
  );
};

إذا أردت تمرير كائنات تشبه الأخطاء إلى شيفرة JavaScript، فاستخدم RCTMakeError من RCTUtils.h، الذي يمرّر حاليًا قاموسًا على شكل خطأ إلى JavaScript، ولكن يهدف React Native إلى إنشاء كائنات خطأ Error حقيقية لشيفرة JavaScript تلقائيًا في المستقبل. يمكنك أيضًا توفير وسيط RCTResponseErrorBlock الذي يُستخدَم لدوال رد نداء الأخطاء ويقبل NSError \* object (لاحظ أن نوع الوسيط هذا لن يدعمه نظام TurboModules).

الوعود Promises

يمكن للوحدات الأصيلة أيضًا أن تفي بالوعود promise، وهذا يبسّط شيفرة JavaScript الخاصة بك، خاصةً عند استخدام صيغة async/await في النسخة ES2016. إذا كان المعامل الأخير لدالة الوحدة الأصيلة هو RCTPromiseResolveBlock و RCTPromiseRejectBlock، فإن دالة JS المقابلة لها ستعيد كائن JS هو كائن الوعد Promise.

تبدو إعادة تصميم الشيفرة السابقة بهدف استخدام وعد بدلًا من دوال رد النداء كما يلي:

RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
                 location:(NSString *)location
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
 NSInteger eventId = createCalendarEvent();
 if (eventId) {
    resolve(@[@(eventId)]);
  } else {
    reject(@"event_failure", @"no event id returned", nil);
  }
}

يعيد نظير JavaScript الخاص بهذا التابع وعدًا (أي كائن ‎‎Promise‎‎). هذا يعني أنه يمكنك استخدام الكلمة المفتاحية await ضمن دالة غير متزامنة لاستدعائها وانتظار نتيجتها:

const onSubmit = async () => {
  try {
    const eventId = await CalendarModule.createCalendarEvent(
      'Party',
      'my house'
    );
    console.log(`Created a new event with id ${eventId}`);
  } catch (e) {
    console.error(e);
  }
};

إرسال الأحداث إلى JavaScript

يمكن للوحدات الأصيلة أن تشير إلى أحداث JavaScript دون استدعائها مباشرة، فقد ترغب مثلًا في إرسال إشارة إلى شيفرة JavaScript لتذكيرها بأن حدث تقويم من تطبيق تقويم iOS الأصيل سيحدث قريبًا. الطريقة المفضلة لتنفيذ ذلك هي إنشاء صنف فرعي من RCTEventEmitter، وتطبيق supportedEvents واستدعاء self sendEventWithName كما يلي:

حدّث صنف الترويسة لاستيراد RCTEventEmitter والصنف الفرعي RCTEventEmitter:

//  CalendarModule.h

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
@end

يمكن أن تشترك شيفرة JavaScript في هذه الأحداث عن طريق إنشاء نسخة جديدة من NativeEventEmitter حول وحدتك الخاصة. ستتلقى تحذيرًا في حالة استهلاك الموارد دون داعٍ عن طريق إرسال حدث عند عدم وجود مستمعين. يمكنك إعادة كتابة startObserving و stopObserving في صنفك الفرعي RCTEventEmitter، لتجنب ذلك التحذير، ولتحسين العبء على الوحدة الخاصة بك (عن طريق إلغاء الاشتراك بالإشعارات الأساسية أو إيقاف مهام الخلفية مؤقتًا على سبيل المثال).

@implementation CalendarManager
{
  bool hasListeners;
}

// ‫ستُستدعَى عند إضافة المستمع الأول لهذه الوحدة.
-(void)startObserving {
    hasListeners = YES;
    // اضبط أي مستمع أساسي أو مهام خلفية حسب الضرورة
}

// ستُستدعَى عند إزالة آخر مستمع لهذه الوحدة، أو عند إلغاء التخصيص
-(void)stopObserving {
    hasListeners = NO;
    // أزِل المستمعين الأساسيين، وأوقِف مهام الخلفية غير الضرورية
}

- (void)calendarEventReminderReceived:(NSNotification *)notification
{
  NSString *eventName = notification.userInfo[@"name"];
  if (hasListeners) { // أرسل الأحداث عند وجود شخص يستمع فقط
    [self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
  }
}

استخدام الخيوط Threading

لا ينبغي أن تَفترِض الوحدة الأصيلة الخيط الذي تُستدعَى عليه، إلّا عندما توفّر طابور التوابع الخاص بها. حاليًا، إن لم توفّر الوحدة الأصيلة طابور التوابع، فسينشئ React Native طابور GCD منفصل لها ويستدعي توابعها هناك (لكن ذلك هو أحد تفاصيل التطبيق وقد يتغير). إذا أردت صراحةً توفير طابور توابع لوحدة أصيلة، فأعِد كتابة التابع (dispatch_queue_t) methodQueue في الوحدة الأصيلة، فإذا احتجت مثلًا إلى استخدام واجهة برمجة تطبيقات iOS ذات الخيط الرئيسي فقط main-thread-only iOS API، فيجب تحديد ذلك عن طريق:

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

وبالمثل، إذا استغرقت عملية وقتًا طويلًا لإكمالها، فيمكن للوحدة الأصيلة تحديد طابورها الخاص لتشغيل العمليات عليه. سيوفّر React Native حاليًا طابورًا منفصلًا للوحدة الأصيلة، ولكنها تفاصيل تطبيق يجب ألّا تعتمد عليها. إن لم توفّر طابور التوابع الخاص بك، فقد تنتهي في المستقبل عمليات وحدتك الأصيلة ذات الوقت الطويل بوقف الاستدعاءات غير المتزامنة التي تُنفَّذ على وحدات أصيلة أخرى غير متعلقة بها. تنشئ الوحدة RCTAsyncLocalStorage هنا على سبيل المثال طابورًا خاصًا بها حتى لا يتوقَّف طابور React مُنتظرًا وصولًا بطيئًا محتملًا إلى القرص:

- (dispatch_queue_t)methodQueue
{
 return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}

سيتشارك جميع التوابع في وحدتك بالتابع methodQueue المُحدَّد. إذا كان تابع واحد فقط من توابعك ذو وقت تشغيل طويل (أو يحتاج إلى تشغيله في طابور بمعزل عن التوابع الأخرى لسبب ما)، فيمكنك استخدام dispatch_async داخل التابع لتشغيل شيفرة هذا التابع على طابور آخر، دون التأثير على الطوابير الأخرى كما يلي:

RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // استدعِ شيفرة ذات وقت تشغيل طويل على خيط خلفيّة
    ...
    // يُمكنك استدعاء دالة رد النداء من أي طابور أو خيط
    callback(@[...]);
 });
}

مشاركة طوابير الإرسال dispatch queues بين الوحدات: سيُستدعَى التابع methodQueue مرة واحدة عند تهيئة الوحدة، ثم سيحتفظ به React Native، لذلك لا حاجة للاحتفاظ بمرجع إلى الطابور بنفسك، إلا إن أردت الاستفادة منه داخل وحدتك. لكن إذا أردت مشاركة الطابور نفسه بين وحدات متعددة، فستحتاج إلى التأكد من أنك تحتفظ وتعيد نسخة الطابور نفسها لكل وحدة.

حقن الاعتماديات Dependency Injection

سينشئ ويهيّئ React Native أي وحدات أصيلة مُسجَّلة تلقائيًا، ولكن قد ترغب في إنشاء وتهيئة نسخ من وحدتك الخاصة لحقن الاعتماديات على سبيل المثال. يمكنك تطبيق ذلك عن طريق إنشاء صنف يطبّق بروتوكول RCTBridgeDelegate وتهيئة RCTBridge باستخدام المفوَّض كوسيط وتهيئة RCTRootView باستخدام الجسر المهيَّأ.

id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];

RCTRootView *rootView = [[RCTRootView alloc]
                        initWithBridge:bridge
                            moduleName:kModuleName
                     initialProperties:nil];

تصدير Swift

لا تدعم لغة Swift الماكرو macros، لذلك يتطلب إظهار الوحدات الأصيلة وتوابعها لشيفرة JavaScript داخل React Native إعدادًا إضافيًّا، ولكنه يعمل نسبيًا بنفس الطريقة. لِنقُل أن لدينا نفس الصنف CalendarManager ولكن كصنفٍ في لغة Swift:

// CalendarManager.swift

@objc(CalendarManager)
class CalendarManager: NSObject {

 @objc(addEvent:location:date:)
 func addEvent(_ name: String, location: String, date: NSNumber) -> Void {
   // ‫التاريخ Date جاهز للاستخدام
 }

 @objc
 func constantsToExport() -> [String: Any]! {
   return ["someKey": "someValue"]
 }

}

يجب استخدام المُعدِّلات objc@ لضمان تصدير الصنف والدّوال تصديرًا صحيحًا إلى وقت تشغيل Objective-C.

ثم أنشِأ ملف تنفيذ خاص ليُسَجِّل المعلومات المطلوبة من React Native:

// CalendarManagerBridge.m
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(CalendarManager, NSObject)

RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)

@end

بالنسبة للأشخاص الجديدين على لغتَي Swift و Objective-C، كلما مزجت اللغتين في مشروع iOS، ستحتاج إلى ملف تجسير bridging file إضافيّ يُعرَف بترويسة التجسير bridging header، لتوفير الوصول إلى ملفات Objective-C من Swift. سيقترح Xcode إنشاء ملف الترويسة هذا لك إذا أضفت ملف Swift إلى تطبيقك من خلال خيار القائمة ملف ‎‎File>New File‎‎. ستحتاج إلى استيراد RCTBridgeModule.h في ملف الترويسة هذا.

// CalendarManager-Bridging-Header.h
#import <React/RCTBridgeModule.h>

يمكنك أيضًا استخدام RCT_EXTERN_REMAP_MODULE و‎‎_RCT_EXTERN_REMAP_METHOD‎‎ لتغيير اسم JavaScript للوحدة أو التوابع التي تُصدِّرها. اطّلع على RCTBridgeModule لمزيد من المعلومات.

ملاحظة مهمّة عند إنشاء وحدات تابعة لطرف ثالث third party modules: المكتبات الساكنة في Swift مدعومة فقط في الإصدار Xcode 9 والإصدارات الأحدث. يجب أن يحتوي مشروع تطبيقك الرئيسي على شيفرة Swift وترويسة التجسير نفسها، لكي يُبنَى مشروع Xcode عند استخدام لغة Swift في مكتبة iOS الساكنة التي تضمّنها في الوحدة. إن لم يتضمّن مشروع تطبيقك أي شيفرة Swift، فيمكن حل المشكلة بملف ‎‎.swift فارغٍ وترويسة تجسير فارغة.

أسماء التوابع المحجوزة

invalidate()‎

يمكن للوحدات الأصيلة أن تتوافق مع بروتوكول RCTInvalidating على نظام iOS من خلال تطبيق التابع invalidate()‎. يمكن استدعاء هذا التابع عند إلغاء الجسر الأصيل (عند إعادة تحميل وضع التطوير devmode على سبيل المثال). يرجى استخدام هذه الآلية حسب الضرورة لإجراء التنظيف المطلوب لوحدتك الأصيلة.

مصادر