פיתוח מודול Native עבור React Native (iOS)
אנו פותרים את הבעיה שאף חבילה מוכנה לא יכולה לכסות: Bluetooth Low Energy באמצעות CoreBluetooth, Keychain מוגן באמצעות SecItemCopyMatching, אינטגרציה של SDK בנקאי או מערכת תשלומים מקומית. כל עוד האפליקציה עובדת רק עם ספריות JS, הכל צפוי. אבל כשעולה צורך לא סטנדרטי, צריך לכתוב Native Module ידנית. וכאן מתחילה עבודת הנדסה אמיתית, שבה הניסיון שלנו של 10 שנים בפיתוח מובייל מבטיח יציבות וביצועים. צרו קשר לייעוץ — נדון בפרויקט שלכם.
סוגי נתונים דרך הגשר
גשר React Native מקבל רק סוגים שניתנים לסריאליזציה ב-JSON: NSString, NSNumber, NSArray, NSDictionary, NSNull. נתונים בינאריים (Data) מקודדים ב-Base64, אובייקטים מותאמים אישית מפורקים למילון בצד ה-Native. זה קריטי במיוחד בעבודה עם CoreBluetooth, שבה צריך להעביר CBCharacteristic עם כל המאפיינים שלו. סריאליזציה שגויה מובילה לשגיאות ריצה שקשה לנפות.
בעיות ש-Native Module פותר
Bluetooth Low Energy. חבילות סטנדרטיות (react-native-ble-plx) לא תמיד תומכות בפרוטוקולים מותאמים אישית או במאפיינים ספציפיים. המודול שלנו עוטף את CoreBluetooth עם שליטה מלאה על CBPeripheral, CBCentralManager וניהול זרימת נתונים.
Keychain ואבטחה. אחסון טוקנים, מפתחות הצפנה, נתונים ביומטריים דורש גישה ישירה ל-SecItemCopyMatching. שגיאות יישום מובילות לדליפות או קריסות — אנו משתמשים בתבנית מוכחת עם בטיחות תהליכונים (Thread Safety) וטיפול נכון בשגיאות.
אינטגרציית SDK. בנקים ומערכות תשלומים רבים מספקים רק ספריות Native (CocoaPods עם Objective-C/Swift). עטיפתם ב-Native Module היא הדרך היחידה להשתמש בהם ב-React Native.
ארכיטקטורת הגשר
לפני הופעת הארכיטקטורה החדשה, הגשר עבד דרך תור הודעות אסינכרוני: תהליך ה-JS סידר את הקריאה ל-JSON, שלח אותה דרך הגשר, ותהליך ה-Native ביצע דה-סריאליזציה וביצע אותה. עיכוב של 5–10 אלפיות השנייה היה מקובל לרוב המשימות, אבל עם קריאות בתדירות גבוהה (למשל, עדכוני UI המבוססים על נתוני חיישנים), הוא הפך למורגש.
עם הופעת הארכיטקטורה החדשה — JSI (JavaScript Interface) + Turbo Modules — התאפשר לקרוא לקוד Native באופן סינכרוני דרך אובייקט מארח ב-C++, תוך עקיפת תור ההודעות. זה משנה מהותית את הגישה: במקום RCTBridgeModule, צריך לממש את פרוטוקול TurboModule באמצעות יצירת קוד המבוססת על מפרט TypeScript.
בפועל, 80% מהפרויקטים עדיין משתמשים בארכיטקטורה הישנה כי עדכון שובר תלויות. לכן, אנו תומכים בשתי הגישות ועוזרים בהגירה הדרגתית. פרטים נוספים על Turbo Modules ניתן למצוא במאגר הרשמי.
| היבט | גשר ישן (RCTBridgeModule) | Turbo Module חדש (JSI) |
|---|---|---|
| מנגנון קריאה | תור הודעות אסינכרוני | קריאה סינכרונית דרך C++ |
| סריאליזציה | JSON (NSString, NSNumber, …) | העברת סוגים ישירה (ללא JSON) |
| עיכוב | 5–10 אלפיות השנייה | <1 אלפית השנייה |
| תאימות | כל גרסת RN | RN עם תמיכת Codegen |
| ביצועים | בינוניים | גבוהים (פי 3–5 מהר יותר) |
ארכיטקטורה ישנה: RCTBridgeModule
מבנה טיפוסי הוא מחלקת Swift היורשת מ-NSObject עם תכונות @objc. רישום דרך RCT_EXTERN_MODULE בקובץ גישור Objective-C הוא חובה — בלעדיו, המודול לא יופיע ברישום.
דוגמת יישום
@objc(BiometricModule)
class BiometricModule: NSObject, RCTBridgeModule {
static func moduleName() -> String {
"BiometricModule"
}
@objc func authenticate(_ reason: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
rejecter("BIOMETRIC_UNAVAILABLE", error?.localizedDescription, error)
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authError in
if success {
resolver(true)
} else {
rejecter("AUTH_FAILED", authError?.localizedDescription, authError)
}
}
}
}הטעות הנפוצה ביותר בשלב זה: מפתח כותב מחלקת Swift, שוכח להוסיף @objc(BiometricModule) class BiometricModule: NSObject, RCTBridgeModule { static func moduleName() -> String { "BiometricModule" } @objc func authenticate(_ reason: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { let context = LAContext() var error: NSError? guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else { rejecter("BIOMETRIC_UNAVAILABLE", error?.localizedDescription, error) return } context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authError in if success { resolver(true) } else { rejecter("AUTH_FAILED", authError?.localizedDescription, authError) } } } } או נותן שם שגוי למתודה ב-RCT_EXTERN_METHOD, ובצד ה-JS מקבל @objc(BiometricModule). קשה לנפות כי השגיאה מופיעה בזמן ריצה ללא stack trace. המהנדס המוסמך שלנו עם ניסיון ב-100+ פרויקטים מבטל שגיאות כאלה בשלב בדיקת הקוד.
איך להבטיח בטיחות תהליכונים?
React Native קורא למתודות המודול על תהליך שרירותי מהמאגר שלו. אם בתוך המתודה ניגשים ל-UIKit, מקבלים קריסה עם undefined is not a function. הפתרון הקלאסי הוא UIKit called from background thread סביב קוד ה-UI. אבל זה יוצר בעיה חדשה: resolve/reject נקראים אסינכרונית, ואם המשתמש סוגר את המסך, ה-completion handler ניגש לאובייקט שכבר שוחרר.
התבנית עם DispatchQueue.main.async { } ו-guard היא חובה:
DispatchQueue.main.async { [weak self] in guard self != nil else { return } resolver(result) } סריאליזציית נתונים. הגשר מקבל רק סוגים שניתנים לסריאליזציה ל-JSON: NSString, NSNumber, NSArray, NSDictionary, NSNull. רוצים להעביר Data? קודדו אותו ב-Base64. רוצים להעביר אובייקט מותאם אישית? פרקו אותו למילון בצד ה-Native. זה כואב במיוחד בעבודה עם CoreBluetooth, שבה צריך לספק CBCharacteristic עם כל המאפיינים שלו.
Callbacks לעומת Promises לעומת Events. לתוצאות חד-פעמיות, השתמשו ב-Promise. לזרם אירועים (נתוני חיישנים, מצב חיבור), השתמשו ב-RCTEventEmitter. ערבוב גישות במודול אחד הוא שגיאה המובילה לדליפות זיכרון: אם RCTResponseSenderBlock נשמר כמאפיין ונקרא פעמיים, האפליקציה קורסת עם [weak self].
ארכיטקטורה חדשה: Turbo Modules + Codegen
החל מגרסת React Native התומכת בארכיטקטורה החדשה, Codegen מייצר הפשטה ב-C++ ממפרט TypeScript. קובץ המפרט נראה כך:
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
authenticate(reason: string): Promise<boolean>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('BiometricModule');
בצד ה-Native, אנו מממשים את פרוטוקול DispatchQueue.main.async { [weak self] in guard self != nil else { return } resolver(result) } , ש-Codegen יצר אוטומטית. JSI מאפשר קריאה למתודות באופן סינכרוני ללא סריאליזציית JSON — המהירות שונה מהותית.
בעיה: אם בפרויקט יש לפחות חבילה אחת ללא תמיכת Turbo Module, הארכיטקטורה החדשה תפעל במצב תאימות, ותאבד חלק מהיתרונות. הצוות שלנו עוזר בביצוע ביקורת תלויות ותכנון הגירה ללא השבתה.
איך לפתח Native Module: תוכנית שלב-אחר-שלב
- קביעת הדרישות ל-API ה-Native ובחירת הארכיטקטורה (Old Bridge או Turbo Module).
- יצירת מחלקת Swift היורשת מ-NSObject והוספת תכונות @objc.
- רישום המודול בקובץ הגישור Objective-C דרך RCT_EXTERN_MODULE.
- יישום מתודות עם Promises או Events, תוך הבטחת בטיחות תהליכונים.
- כתיבת סוגי TypeScript עבור ה-API הציבורי.
- כיסוי הקוד ה-Native בבדיקות יחידה (XCTest).
- בדיקת האינטגרציה עם שכבת ה-JS בסימולטור ובמכשיר אמיתי.
גישת היישום
אנו מתחילים בביקורת של גרסת ה-RN הנוכחית, נוכחות חבילות תואמות JSI, וגרסת ה-iOS היעד. אם הפרויקט בגרסה התומכת בארכיטקטורה החדשה והצוות מוכן, אנו כותבים מיד Turbo Module עם Codegen. אם לא, אנו משתמשים ב-RCTBridgeModule הקלאסי עם מבט לעתיד הגירה.
כיסוי החלק ה-Native בבדיקות יחידה דרך XCTest הוא חובה. בדיקות אינטגרציה נעשות עם Detox או Jest עם מודול מדומה בצד ה-JS. זה מפחית את מספר הבאגים בגרסת ה-release ב-40%.
אנו מתעדים את ה-API הציבורי בסוגי TypeScript כך שהצוות לא יצטרך לצלול לקוד ה-Native בכל פעם. התוצאה היא האצה פי 2 של תהליך הקליטה למפתחים חדשים.
| טעות נפוצה | השלכה | פתרון |
|---|---|---|
| חוסר @objc על המחלקה | המודול לא נרשם | הוספת @objc(ModuleName) |
| קריאה ל-UIKit ללא main.async | קריסת אפליקציה | DispatchQueue.main.async |
| קריאה כפולה ל-callback | קריסת אפליקציה | שימוש ב-Promise או guard |
| העברת אובייקט מותאם אישית | שגיאת סריאליזציה | פירוק ל-NSDictionary |
מה כלול בעבודה
- ניתוח דרישות ובחירת גישה ארכיטקטונית (Old Bridge / Turbo Module)
- כתיבת קוד Native ב-Swift עם גישור Objective-C
- ת打字 TypeScript של ה-API הציבורי של המודול
- טיפול בשגיאות, בטיחות תהליכונים
- בדיקות יחידה לחלק ה-Native (XCTest)
- אינטגרציה עם שכבת ה-JS, אימות בסימולטור ובמכשיר אמיתי
- תיעוד שימוש במודול
לוחות זמנים
מ-3 עד 5 ימים בהתאם למורכבות ה-API ה-Native שיש לעטוף. עטיפה פשוטה של framework מערכתי אחד אורכת כ-3 ימים. מודול עם זרם אירועים, נתונים בינאריים ותמיכה בארכיטקטורה החדשה אורך 5 ימים או יותר. העלות מחושבת באופן אישי לאחר ניתוח דרישות ובדיקת קוד.
קבלו ייעוץ — צרו קשר להערכה מקדימה של הפרויקט שלכם. הזמינו פיתוח Native Module מאיתנו — אנו מבטיחים יציבות וביצועים.







