الفرق بين المراجعتين لصفحة: «ReactNative/native modules ios»
لا ملخص تعديل |
لا ملخص تعديل |
||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE: الوحدات الأصيلة (iOS) في React Native}}</noinclude> | <noinclude>{{DISPLAYTITLE: الوحدات الأصيلة (iOS) في React Native}}</noinclude> | ||
يُرجى الاطّلاع أولًا على صفحة [[ReactNative/native modules intro|مدخل إلى الوحدات الأصيلة Native Modules]] للتعرّف على الوحدات الأصيلة. | |||
== إنشاء وحدة التقويم الأصيلة كمثال 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> | |||
=== الإعداد === | |||
افتح أولًا مشروع iOS داخل تطبيق React Native الخاص بك في Xcode. يمكنك العثور على مشروع iOS الخاص بك داخل تطبيق React Native كما في الشكل التالي: | |||
[[ملف:native-modules-ios-open-project.png|بديل=native modules ios open project|مركز|تصغير|344x344بك|مكان العثور على مشروع iOS الخاص بك]] | |||
نوصيك باستخدام Xcode لكتابة شيفرتك الأصيلة، حيث بُنِي Xcode لتطوير تطبيقات iOS، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة. | |||
=== إنشاء ملفات الوحدة الأصيلة المخصَّصة === | |||
تتمثل الخطوة الأولى في إنشاء ترويسة الوحدة الأصيلة المخصَّصة وملفات التنفيذ. أنشئ ملفًا جديدًا بالاسم <code>RCTCalendarModule.h</code> كما يلي: | |||
[[ملف:native-modules-ios-add-class.png|بديل=native modules ios add class|مركز|تصغير|330x330بك|إنشاء ملف وحدة أصيلة مخصصَّة ضمن نفس مجلد AppDelegate]] | |||
وأضِف ما يلي إلى هذا الملف:<syntaxhighlight lang="obj-c"> | |||
// RCTCalendarModule.h | |||
#import <React/RCTBridgeModule.h> | |||
@interface RCTCalendarModule : NSObject <RCTBridgeModule> | |||
@end | |||
</syntaxhighlight>يمكنك استخدام أيّ اسم يناسب الوحدة الأصيلة التي تنشئها، إذ يمكنك تسمية الصنف <code>RCTCalendarModule</code> بما أنك تنشئ وحدة تقويم أصيلة. لا تحتوي لغة ObjC دعمًا على مستوى اللغة لفضاءات الأسماء مثل لغتي Java أوC++، لذلك يجب أن سبق سلسلةٌ نصية فرعية اسمَ الصنف، وقد تكون هذه السلسلة النصية اختصارًا لاسم تطبيقك أو لاسم بنيته التحتية، إذ تشير RCT في هذا المثال إلى React. | |||
يطبّق الصنفُ <code>CalendarModule</code> بروتوكولَ <code>RCTBridgeModule</code> كما سترى أدناه، فالوحدة الأصيلة هي صنف Objective-C الذي يطبّق بروتوكول <code>RCTBridgeModule</code>. | |||
لنبدأ بعد ذلك في تطبيق الوحدة الأصيلة. أنشئ ملف التنفيذ المقابل <code>RCTCalendarModule.m</code> في نفس المجلد وضمِّن ما يلي:<syntaxhighlight lang="obj-c"> | |||
// RCTCalendarModule.m | |||
#import "RCTCalendarModule.h" | |||
@implementation RCTCalendarModule | |||
// لتصدير وحدة بالاسم RCTCalendarModule | |||
RCT_EXPORT_MODULE(); | |||
@end | |||
</syntaxhighlight> | |||
=== اسم الوحدة === | |||
تشتمل حاليًا الوحدة الأصيلة <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"> | |||
// لتصدير وحدة بالاسم CalendarModuleFoo | |||
RCT_EXPORT_MODULE(CalendarModuleFoo); | |||
</syntaxhighlight>يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JS كما يلي:<syntaxhighlight lang="javascript"> | |||
const { CalendarModuleFoo } = ReactNative.NativeModules; | |||
</syntaxhighlight>إذا لم تحدد اسمًا، فسوف يتطابق اسم وحدة JavaScript مع اسم صنف Objective-C، مع إزالة البادئات مثل "RCT" أو "RK". | |||
لنستدعي الآن <code>RCT_EXPORT_MODULE</code> دون وسطاء. ستظهر الوحدة لإطار عمل React Native بالاسم <code>CalendarModule</code>، بما أنه اسم صنف Objective-C، ولكن دون RCT.<syntaxhighlight lang="obj-c"> | |||
// سيؤدي عدم تمرير الاسم إلى تصدير اسم الوحدة الأصيلة كاسم صنف Objective-C دون "RCT" | |||
RCT_EXPORT_MODULE(); | |||
</syntaxhighlight>ثم يمكن الوصول إلى الوحدة الأصيلة في شيفرة JS كما يلي:<syntaxhighlight lang="javascript"> | |||
const { CalendarModule } = ReactNative.NativeModules; | |||
</syntaxhighlight> | |||
=== تصدير طريقة أصيلة إلى 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"> | |||
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"> | |||
#import <React/RCTLog.h> | |||
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location) | |||
{ | |||
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); | |||
} | |||
</syntaxhighlight> | |||
=== التوابع المتزامنة === | |||
يمكنك استخدام <code>RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD</code> لإنشاء تابع أصيل متزامن كما يلي:<syntaxhighlight lang="javascript"> | |||
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName) | |||
{ | |||
return [[UIDevice currentDevice] name]; | |||
} | |||
</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. | |||
=== اختبر ما بنيته === | |||
أجريت حتى الآن الإعداد الأساسي لوحدتك الأصيلة في iOS. اختبر هذا الإعداد من خلال الوصول إلى الوحدة الأصيلة واستدعاء تابع تصديرها في شيفرة JavaScript. | |||
ابحث عن مكانٍ ما في تطبيقك حيث تريد إضافة استدعاء تابع الوحدة الأصيلة <code>createCalendarEvent()</code>. يحتوي المثال التالي المكوّن <code>NewModuleButton</code> الذي يمكنك إضافته إلى تطبيقك، حيث يمكنك استدعاء الوحدة الأصيلة ضمن الدالة <code>onPress()</code> الخاصة بالمكوّن <code>NewModuleButton</code>:<syntaxhighlight lang="javascript"> | |||
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; | |||
</syntaxhighlight>يجب استيراد <code>NativeModules</code> من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة JavaScript:<syntaxhighlight lang="javascript"> | |||
import { NativeModules } from 'react-native'; | |||
</syntaxhighlight>ثم يمكنك الوصول إلى الوحدة الأصيلة <code>CalendarModule</code> من خارج <code>NativeModules</code>:<syntaxhighlight lang="javascript"> | |||
const { CalendarModule } = NativeModules; | |||
</syntaxhighlight>يمكنك الآن استدعاء التابع الأصيل <code>createCalendarEvent()</code> بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى التابع <code>onPress()</code> في المكوّن <code>NewModuleButton</code>:<syntaxhighlight lang="javascript"> | |||
const onPress = () => { | |||
CalendarModule.createCalendarEvent('testName', 'testLocation'); | |||
}; | |||
</syntaxhighlight>الخطوة الأخيرة هي إعادة بناء تطبيق React Native بحيث يمكنك الحصول على أحدث شيفرة أصيلة (مع الوحدة الأصيلة الجديدة). شغّل الأمر التالي في سطر الأوامر ضمن المكان الذي يوجد فيه تطبيق react native:<syntaxhighlight lang="bash"> | |||
npx react-native run-ios | |||
</syntaxhighlight> | |||
=== إعادة البناء عند التكرار === | |||
ستحتاج أثناء تكرار وحدتك الأصيلة إلى إعادة بناء أصيلة لتطبيقك بهدف الوصول إلى أحدث التغييرات من شيفرة JavaScript، لأن الشيفرة التي تكتبها موجودة ضمن الجزء الأصيل من تطبيقك. يمكن لمجمّع metro الخاص بإطار عمل React Native مراقبة التغييرات في شيفرة JavaScript وإعادة إنشاء حزمة JS سريعًا نيابةً عنك، إلّا أنه لن يفعل ذلك مع الشيفرة الأصيلة. لذلك إذا أردت اختبار أحدث التغييرات الأصيلة، فيجب إعادة البناء باستخدام الأمر <code>npx react-native run-ios</code>. | |||
=== الخلاصة === | |||
يجب أن تكون الآن قادرًا على استدعاء التابع <code>createCalendarEvent()</code> للوحدة الأصيلة في شيفرة JavaScript. بما أنك تستخدم <code>RCTLog</code>، فيمكنك تأكيد استدعاء تابعك الأصيل من خلال [[ReactNative/debugging#.D8.A3.D8.AF.D9.88.D8.A7.D8.AA .D9.85.D8.B7.D9.88.D8.B1.D9.8A Chrome|تفعيل وضع تنقيح الأخطاء في تطبيقك]] والنظر إلى وحدة تحكم JS في Chrome أو منقّح أخطاء تطبيقات الهواتف المحمولة Flipper. | |||
يجب أن ترى رسالة <code>RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);</code> في كل مرة تستدعي فيها تابع الوحدة الأصيلة. | |||
[[ملف:native-modules-ios-logs.png|بديل=native modules ios logs|مركز|تصغير|373x373بك|سجلات iOS في Flipper]] | |||
أنشأت حتى الآن وحدة iOS أصيلة واستدعيت تابعًا لها من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد توابع رد النداء والوعود ضمن وحدتك الأصيلة. | |||
تحتاج التطبيقات أحيانًا إلى الوصول إلى واجهة برمجة (API) منصةٍ ما، وReact Native لا يحتوي على وحدة ملائمة لهذا الغرض حتى الآن. قد ترغب في إعادة استخدام بعض شيفرات Objective-C أو Swift أو C++ الموجودة مسبقا دون الحاجة إلى إعادة كتابتها بلغة JavaScript أو كتابة بعض الشيفرات عالية الأداء أو متعددة السلاسل (multi-threaded) مثل معالجة الصور أو قاعدة بيانات أو أي عدد من الإضافات المتقدمة. | تحتاج التطبيقات أحيانًا إلى الوصول إلى واجهة برمجة (API) منصةٍ ما، وReact Native لا يحتوي على وحدة ملائمة لهذا الغرض حتى الآن. قد ترغب في إعادة استخدام بعض شيفرات Objective-C أو Swift أو C++ الموجودة مسبقا دون الحاجة إلى إعادة كتابتها بلغة JavaScript أو كتابة بعض الشيفرات عالية الأداء أو متعددة السلاسل (multi-threaded) مثل معالجة الصور أو قاعدة بيانات أو أي عدد من الإضافات المتقدمة. | ||
مراجعة 02:45، 3 يوليو 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 كما في الشكل التالي:
نوصيك باستخدام Xcode لكتابة شيفرتك الأصيلة، حيث بُنِي Xcode لتطوير تطبيقات iOS، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.
إنشاء ملفات الوحدة الأصيلة المخصَّصة
تتمثل الخطوة الأولى في إنشاء ترويسة الوحدة الأصيلة المخصَّصة وملفات التنفيذ. أنشئ ملفًا جديدًا بالاسم RCTCalendarModule.h
كما يلي:
وأضِف ما يلي إلى هذا الملف:
// 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);
في كل مرة تستدعي فيها تابع الوحدة الأصيلة.
أنشأت حتى الآن وحدة iOS أصيلة واستدعيت تابعًا لها من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد توابع رد النداء والوعود ضمن وحدتك الأصيلة.
تحتاج التطبيقات أحيانًا إلى الوصول إلى واجهة برمجة (API) منصةٍ ما، وReact Native لا يحتوي على وحدة ملائمة لهذا الغرض حتى الآن. قد ترغب في إعادة استخدام بعض شيفرات Objective-C أو Swift أو C++ الموجودة مسبقا دون الحاجة إلى إعادة كتابتها بلغة JavaScript أو كتابة بعض الشيفرات عالية الأداء أو متعددة السلاسل (multi-threaded) مثل معالجة الصور أو قاعدة بيانات أو أي عدد من الإضافات المتقدمة.
صُمِّم React Native بحيث يمكنك كتابة شيفرة أصيلة حقيقية مع التحكم الكامل بالمنصة. هذه ميزة أكثر تقدمًا ولا نتوقع أن تكون جزءًا من عملية التطوير المعتادة، ولكن وجودها ضروري. إذا كان React Native لا يدعم ميزة أصيلة تحتاجها، يجب أن تكون قادرًا على بنائها بنفسك.
هذا دليل أكثر تقدمًا يوضح كيفية إنشاء وحدة أصيلة. يُفترَض أن القارئ يعرف لغة Objective-C أو Swift والمكتبات الأساسية (Foundation، وUIKit).
إعداد الوحدات الأصيلة
عادة ما تُوزَّع الوحدات الأصيلة كحزم npm، إلا أنه لكي تكون وحداتٍ أصيلة، فستحتوي على مكتبة Xcode. للحصول على الهيكل الأساسي، تأكد من قراءة دليل إعداد الوحدات الأصيلة أولاً.
وحدة تقويم iOS كمثال (iOS Calendar Module)
سيستخدم هذا الدليل واجهة برمجة تقويم iOS كمثال. لنفترض أننا نريد الوصول إلى تقويم iOS من JavaScript.
الوحدات الأصيلة مجرد أصناف Objective-C تُنفِّذ (أو تعتمد على) بروتوكول RCTBridgeModule
. المقطع RCT
اختصارٌ لكلمة ReaCT.
// CalendarManager.h
#import <React/RCTBridgeModule.h>
@interface CalendarManager : NSObject <RCTBridgeModule>
@end
بالإضافة إلى تطبيق بروتوكول RCTBridgeModule
، يجب أن يتضمن صنفك أيضًا الماكرو RCT_EXPORT_MODULE()
، الذي يأخذ معاملا اختياريا يحدد الاسم الذي سيتم الوصول به إلى الوحدة في شيفرة JavaScript الخاصة بك (المزيد حول هذا لاحقًا). إذا لم تحدد اسمًا، فسيتطابق اسم وحدة JavaScript مع اسم صنف Objective-C. إذا بدأ اسم صنف Objective-C بالمقطع RCT، فستستثني JavaScript البادئة RCT
من اسم الوحدة.
// CalendarManager.m
#import "CalendarManager.h"
@implementation CalendarManager
// لتصدير وحدةٍ باسم
// CalendarManager
RCT_EXPORT_MODULE();
// سيُسمِّي هذا السّطر الوحدةَ بالاسم
// AwesomeCalendarManager
// RCT_EXPORT_MODULE(AwesomeCalendarManager);
@end
لن يوفر React Native الوصول إلى أي تابع من توابع الصنف CalendarManager
للغة JavaScript ما لم يُعلَم بذلك بوضوح. وذلك باستخدام الماكرو RCT_EXPORT_METHOD()
:
#import "CalendarManager.h"
#import <React/RCTLog.h>
@implementation CalendarManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
@end
يمكنك الآن استدعاء التابع من ملف JavaScript الخاص بك بهذا الشكل:
import {NativeModules} from 'react-native';
var CalendarManager = NativeModules.CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
ملاحظة حول أسماء توابع JavaScript
اسم التابع المُصدَّر إلى JavaScript هو اسم التابع الأصيل إلى غاية أول نقطتين (:
). يعرِّف React Native أيضًا ماكرو يُسمى RCT_REMAP_METHOD()
لتحديد اسم تابع JavaScript. هذا مفيدٌ عندما تكون توابع أصيلة متعددة هي نفسها إلى غاية أول نقطتين وفي حالة تضمّنت أسماء JavaScript متعارضة.
تُهيَّأ وحدة CalendarManager
في جهة لغة Objective-C باستخدام استدعاء [CalendarManager new]
. النوع المُعاد (return type) لتوابع الجسر (bridge methods) يكون دائمًا void
.
جسر React Native غير متزامن (asynchronous)، لذا فإن الطريقة الوحيدة لتمرير نتيجة إلى JavaScript هي باستخدام دوال رد النداء (callbacks) أو بعث الأحداث (emitting events)، انظر أدناه للمزيد.
أنواع المعاملات
يدعم RCT_EXPORT_METHOD
جميع أنواع كائنات JSON القياسية، مثل:
- السلاسل النصيّة (
NSString
) - الأعداد (
NSInteger
,float
,double
,CGFloat
,NSNumber
) - القيم المنطقيّة (
BOOL
,NSNumber
) - المصفوفات (
NSArray
) التي تحتوي على أي نوع من قائمة الأنواع هذه. - الكائنات (
NSDictionary
) ذات مفاتيحٍ نصيّة وقيمٍ من أي نوع من قائمة الأنواع هذه. - الدوال (
RCTResponseSenderBlock
)
إضافة إلى دعم أي نوع من الأنواع التي يدعمها الصنف RCTConvert
(انظر RCTConvert للمزيد). تقبل جميع دوال RCTConvert
المساعِدةُ قيمةَ JSON كمدخلات وتربطها بنوع أو صنف Objective-C أصيل.
في مثالنا CalendarManager
، نحتاج إلى تمرير تاريخ الحدث إلى التابع الأصيل. لا يمكننا إرسال كائنات تاريخ JavaScript (كائنات Date
) عبر الجسر، لذلك نحتاج إلى تحويل التاريخ إلى سلسلة نصيّة أو عدد. يمكننا كتابة دالتنا الأصيلة هكذا:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)secondsSinceUnixEpoch)
{
NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch];
}
أو هكذا:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString)
{
NSDate *date = [RCTConvert NSDate:ISO8601DateString];
}
ولكن باستخدام ميزة التحويل التلقائي للأنواع، فيمكننا تخطي خطوة التحويل اليدوي بالكامل، وفقط كتابة ما يلي:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date)
{
// التاريخ جاهز للاستخدام الآن
}
بعد ذلك سيكون الاستدعاء في جهة JavaScript إما:
CalendarManager.addEvent(
'Birthday Party',
'4 Privet Drive, Surrey',
date.getTime(),
); // passing date as number of milliseconds since Unix epoch
// تمرير التاريخ كعدد الأجزاء من ألف جزء من الثانية منذ زمن
// Unix
أو
CalendarManager.addEvent(
'Birthday Party',
'4 Privet Drive, Surrey',
date.toISOString(),
);
// تمرير التاريخ كسلسلة نصية بتنسيق
// ISO-8601
وستُحوَّل كلا القيمتان بشكل صحيح إلى النوع NSDate
الأصيل. من شأن قيمةٍ سيئةٍ، كمصفوفةٍ (Array
)، توليد رسالة خطأ صندوق أحمر ("RedBox") مفيدة.
كلّما ازداد تابع CalendarManager.addEvent
تعقيدًا، كلّما ازداد عدد المعاملات. وقد يكون بعضها اختياريًّا. في هذه الحالة، من المفضّل تغيير واجهة برمجة التطبيقات قليلاً لقبول قاموس لسمات الأحداث (event attributes) كما يلي:
#import <React/RCTConvert.h>
RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details)
{
NSString *location = [RCTConvert NSString:details[@"location"]];
NSDate *time = [RCTConvert NSDate:details[@"time"]];
...
}
الاستدعاء من لغة JavaScript:
CalendarManager.addEvent('Birthday Party', {
location: '4 Privet Drive, Surrey',
time: date.getTime(),
description: '...',
});
ملاحظة: حول المصفوفات (array) والترابطات (map)
لا توفر لغة Objective-C أي ضمانات حول أنواع القيم في هذه الهياكل. قد تتوقع وحدتك الأصيلة مصفوفة من السلاسل النصية، ولكن إذا استدعَتْ JavaScript تابِعَك باستخدام مصفوفة تحتوي على أرقام وسلاسل نصيّة، فستحصل على مصفوفة NSArray
تحتوي على مزيج من أعداد NSNumber
وسلاسل NSString
النّصيّة. بالنسبة للمصفوفات، يوفر RCTConvert
بعض المجموعات المنوَّعة (typed collections) التي يمكنك استخدامها في تعريف تابعك، مثل NSStringArray
أو UIColorArray
. بالنسبة للترابطات، تقع مسؤولية التحقق من أنواع القيم كل على حدة على عاتق المطور عبر استدعاء توابع RCTConvert
المساعِدة يدويًا.
دوال رد النداء
تحذير: هذا القسم في مرحلة تجريبية أكثر من الأقسام الأخرى، إذ لا وجود لمجموعة جيّدة من أفضل الممارسات (best practices) حول دوال رد النداء حتى الآن.
تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات: والذي يتمثّل في دوال رد نداء. والتي تُستخدم في معظم الحالات لتوفير نتيجة استدعاءِ دالةٍ للغة JavaScript.
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
NSArray *events = ...
callback(@[[NSNull null], events]);
}
يقبل RCTResponseSenderBlock
معاملًا واحدا فقط، وهو مصفوفةٌ من المعاملات لتمريرها إلى دالة رد نداء JavaScript. في هذه الحالة، نستخدم عُرفَ Node في جعل المعامل الأوَّل كائنَ خطأ (عادةً ما يكون القيمة null
في حالة عدم وجود خطأ) والباقي هي نتائج الدالة.
CalendarManager.findEvents((error, events) => {
if (error) {
console.error(error);
} else {
this.setState({events: events});
}
});
يجب أن تستدعي الوحدةُ الأصيلة دالة رد النداء الخاصة بها مرة واحدة فقط. لا بأس في تخزين دالة رد النداء واستدعائها لاحقًا. غالبًا ما يُستخدَم هذا النمط لتغليف واجهات برمجة تطبيقات iOS التي تتطلب مفوضين (delegates)، انظر RCTAlertManager
كمثال. إذا لم تُستدعَى دالة رد النداء مطلقًا، فسيُسرَّب جزء من الذاكرة. إذا مُرِّرَت كل من دالتي رد النداء onSuccess
و onFail
، فينبغي استدعاء واحدة منهما فقط.
إذا كنت ترغب في تمرير كائنات الأخطاء أو ما شبهها (error-like objects) إلى JavaScript، فاستخدم RCTMakeError
من RCTUtils.h
. في الوقت الحالي، يقوم هذا بتمرير قاموس على شكل خطأ إلى JavaScript، لكننا (أي فريق React Native) نرغب في إنشاء كائنات Error
حقيقية للغة JavaScript تلقائيا في المستقبل.
الوعود (Promises)
يمكن للوحدات الأصيلة أيضًا الوفاء بالوعود، ما يمكن أن يبسط شيفرتك، خاصة عند استخدام بنية async/await
الجديدة في نسخة ES2016. عندما تكون المعاملات الأخيرة للتابع الأصيل الموصَل عن طريق الجسر هي RCTPromiseResolveBlock
وRCTPromiseRejectBlock
، فسيُعيد تابع JavaScript الخاص به والمقابل له كائنَ Promise
في JavaScript.
ستبدو إعادة تصميم الشيفرة أعلاه لاستخدام وعدٍ بدلاً من دوال رد النداء كما يلي:
RCT_REMAP_METHOD(findEvents,
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSArray *events = ...
if (events) {
resolve(events);
} else {
NSError *error = ...
reject(@"no_events", @"There were no events", error);
}
}
يقوم نظير JavaScript الخاص بهذا التابع بإرجاع وعد (أي كائن Promise
). هذا يعني أنه يمكنك استخدام الكلمة المفتاحيّة await
داخل دالة غير متزامنة (async function) لاستدعائها وانتظار نتيجتها:
async function updateEvents() {
try {
var events = await CalendarManager.findEvents();
this.setState({events});
} catch (e) {
console.error(e);
}
}
updateEvents();
التسلسل Threading
لا ينبغي أن تَفترِض الوحدة الأصيلة ماهية السلسلة التي يتم استدعاؤها عليها. تستدعي React Native توابع الوحدات الأصيلة في طابور GCD تسلسليّ منفصل، ولكن هذا تفصيل إجراء (implementation detail) وقد يتغير. يسمح التابع - (dispatch_queue_t)methodQueue
للوحدة الأصيلة بتحديد الطابور الذي يجب تشغيل توابعها عليه. على سبيل المثال، إذا كنت تحتاج إلى استخدام واجهة برمجة تطبيقات iOS ذات السلسلة الرئيسية فقط (main-thread-only iOS API)، فيجب تحديد ذلك عبر:
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
وبالمثل، إذا كانت عملية ما قد تستغرق وقتًا طويلاً لإكمالها، فلا يجب إيقاف الوحدة الأصيلة وينبغي أن تتمكّن من تحديد الطابور الخاص بها لتشغيل العمليات عليه. على سبيل المثال، تقوم الوحدة 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
مرة واحدة عند تهيئة الوحدة، ثم سيُحتفَظ به بواسطة الجسر، لذلك لا حاجة للاحتفاظ بالطابور بنفسك، إلا إن أردت الاستفادة منه داخل وحدتك. ومع ذلك، إذا أردت مشاركة الطابور نفسه بين وحدات متعددة، فستحتاج إلى التأكد من أنك تحتفظ بنفس نسخة الطابور (queue instance) وتعيدها لكل منها؛ إذ لن تنجح مجرد إعادة الطابور بنفس الاسم لكل منها.
حقن الاعتماديات (Dependency Injection)
يُهيّئ الجسر تلقائيًّا الوحدات RCTBridgeModules
المسجلة، لكن قد ترغب في تهيئة نسخ وحدات خاصّة بك (لحقن الاعتماديات مثلًا).
يمكنك ذلك عبر إنشاء صنف يُجرِي (أو يعتمد على) بروتوكول 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];
تصدير الثوابت (Exporting Constants)
يمكن لوحدة أصيلة تصدير الثوابت المتاحة حينًا (immediately available) إلى لغة JavaScript في وقت التشغيل (runtime). هذا مفيد لنقل البيانات الساكنة (static data) التي ستتطلب رحلة ذهاب وإياب عبر الجسر في غير ذلك من حالات.
- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };
}
يمكن للغة JavaScript استخدام هذه القيمة على الفور بشكل متزامن:
console.log(CalendarManager.firstDayOfTheWeek);
لاحظ أن الثوابت تُصدَّر فقط في وقت التهيئة، لذلك إذا غيرت قيم constantsToExport
في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript.
إجراء + requiresMainQueueSetup
إذا تجاوزت (أو أبطلت [override]) - constantsToExport
، فعليك أيضًا إجراء + requiresMainQueueSetup
للسماح لإطار React Native بمعرفة ما إذا كانت وحدتك بحاجة إلى تهيئتها في السلسلة الرئيسية. وإلا سترى تحذيرًا بأنه في المستقبل قد تتم تهيئة وحدتك على سلسلة في الخلفية ما لم تكن قد قمت صراحةً بإلغاء الاشتراك مع + requiresMainQueueSetup
:
+ (BOOL)requiresMainQueueSetup
{
// استعمل هذا فقط في حالة كانت تهيئة وحدتك تعتمد على استدعاء
// UIKit!
return YES;
}
إذا كانت وحدتك لا تتطلب الوصول إلى UIKit، فيجب عليك الرد على + requiresMainQueueSetup
بالقيمة NO
.
الثوابت المعددة (Enum Constants)
لا يمكن استخدام الثوابت المُعدَّدَة المعرَّفة من خلال NS_ENUM
كمعاملاتِ توابعٍ (method arguments) دون توسيع RCTConvert
أولاً.
لتصدير تعريف NS_ENUM
التالي:
typedef NS_ENUM(NSInteger, UIStatusBarAnimation) {
UIStatusBarAnimationNone,
UIStatusBarAnimationFade,
UIStatusBarAnimationSlide,
};
يجب عليك إنشاء توسِعة صنفٍ (class extension) للصنف RCTConvert
هكذا:
@implementation RCTConvert (StatusBarAnimation)
RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),
@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),
@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)}),
UIStatusBarAnimationNone, integerValue)
@end
يمكنك بعد ذلك تحديد التوابع وتصدير الثوابت المعدّدة الخاصة بك هكذا:
- (NSDictionary *)constantsToExport
{
return @{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),
@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),
@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide) };
};
RCT_EXPORT_METHOD(updateStatusBarAnimation:(UIStatusBarAnimation)animation
completion:(RCTResponseSenderBlock)callback)
سيُلغى بعد ذلك تغليف ثابتك المُعدَّد تلقائيًا باستخدام المُحدِّد المُوفَّر (integerValue
في المثال أعلاه) قبل تمريره إلى التابع المُصدَّر.
إرسال الأحداث إلى JavaScript
يمكن للوحدة الأصيلة أن تشير إلى الأحداث (signal events) إلى JavaScript دون استدعائها مباشرة. الطريقة المفضلة للقيام بذلك هي إنشاء صنف فرعي من RCTEventEmitter
، وإجراء supportedEvents
واستدعاء self sendEventWithName
:
// CalendarManager.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
@end
// 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
يمكن لشيفرة JavaScript الاشتراك (subscribe) في هذه الأحداث من خلال إنشاء نسخة NativeEventEmitter
جديدة حول وحدتك.
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();
لمزيد من أمثلة إرسال الأحداث إلى JavaScript، راجع RCTLocationObserver
.
تحسين الأداء عند انعدام المستمعين (Optimizing for zero listeners)
ستتلقى تحذيرًا إذا كنت تستعمل الموارد بلا حاجة عن طريق إرسال حدث أثناء عدم وجود مستمعين. لتجنب هذا الأمر، ولتحسين عبء العمل على وحدتك (على سبيل المثال عن طريق إلغاء الاشتراك من التنبيهات الأولية أو إيقاف مهام الخلفية مؤقتًا)، يمكنك تجاوز startObserving
وstopObserving
في الصنف الفرعي RCTEventEmitter
الخاص بك.
@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}];
}
}
تصدير Swift
دعم الماكرو غير موجود في لغة Swift، لذا فإن توفير الوصول إليها لإطار React Native يتطلب إعدادًا إضافيًّا، ولكنه يعمل نسبيًا بنفس الشكل.
لِنقُل أن لدينا نفس الصنف CalendarManager
ولكن كصنفٍ في لغة Swift:
// CalendarManager.swift
@objc(CalendarManager)
class CalendarManager: NSObject {
@objc(addEvent:location:date:)
func addEvent(name: String, location: String, date: NSNumber) -> Void {
// التاريخ جاهز للاستخدام
}
@objc
func constantsToExport() -> [String: Any]! {
return ["someKey": "someValue"]
}
}
ملاحظة: من المهم استخدام المُعدِّل objc@
لضمان تصدير الصنف والدّوال بشكل صحيح إلى وقت تشغيل Objective-C.
ثم أنشِأ ملف إجراءٍ خاص (private implementation file) ليُسَجِّل المعلومات المطلوبة مع جسر 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 والإصدارات الأحدث. لكي يُبنَى مشروع Xcode عند استخدام Swift في مكتبة iOS الساكنة التي تدرجها في الوحدة، يجب أن يحتوي مشروع تطبيقك الرئيسي على شيفرة Swift وترويسة التجسير نفسها. إذا كان مشروع تطبيقك لا يحتوي على أي شيفرة Swift، فيمكن حل المشكلة بملف .swift
فارغٍ وترويسة تجسير فارغة.