Native Module для React Native на iOS: создание с нуля

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Native Module для React Native на iOS: создание с нуля
Сложный
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    860
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    747
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1163
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1036
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    970
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    564

Разработка Native Module для React Native (iOS)

Мы решаем задачу, которую не закрыть готовым пакетом: Bluetooth Low Energy через CoreBluetooth, защищённый Keychain через SecItemCopyMatching, интеграция нативного SDK банка или платёжной системы. Пока приложение работает только с JS-библиотеками, всё предсказуемо. Но когда появляется нестандартная потребность — приходится писать Native Module вручную. И здесь начинается настоящая инженерная работа, где наш 10-летний опыт в мобильной разработке даёт гарантию стабильности и производительности. Свяжитесь с нами для консультации — обсудим ваш проект.

Типы данных через мост

Мост React Native принимает только типы, сериализуемые в JSON: NSString, NSNumber, NSArray, NSDictionary, NSNull. Бинарные данные (Data) кодируйте в Base64, кастомные объекты разбирайте в словарь на нативной стороне. Это особенно критично при работе с CoreBluetooth, где надо передавать CBCharacteristic со всеми свойствами. Неправильная сериализация приводит к ошибкам времени выполнения, которые сложно отладить.

Проблемы, решаемые Native Module

Bluetooth Low Energy. Стандартные пакеты (react-native-ble-plx) не всегда поддерживают кастомные протоколы или специфические характеристики. Наш модуль оборачивает CoreBluetooth с полным контролем над CBPeripheral, CBCentralManager и управлением потоком данных.

Keychain и безопасность. Хранение токенов, ключей шифрования, биометрических данных требует прямого доступа к SecItemCopyMatching. Ошибки в имплементации приводят к утечкам или крашам — мы используем проверенный шаблон с потокобезопасностью и корректной обработкой ошибок.

Интеграция SDK. Многие банки и платёжные системы предоставляют только нативные библиотеки (CocoaPods с Objective-C/Swift). Обёртка в Native Module — единственный путь к их использованию в React Native.

Архитектура моста

До появления New Architecture мост работал через асинхронную очередь сообщений: JS-поток сериализовал вызов в JSON, отправлял через мост, нативный поток десериализовал и исполнял. Задержка 5–10 мс была приемлемой для большинства задач, но при высокочастотных вызовах (например, обновление UI по данным с датчиков) она становилась заметной.

С появлением New Architecture — JSI (JavaScript Interface) + Turbo Modules — появилась возможность вызывать нативный код синхронно через C++ host object, минуя очередь сообщений. Это принципиально меняет подход: вместо RCTBridgeModule нужно реализовать TurboModule-протокол через кодогенерацию на основе TypeScript-спецификации.

На практике 80% проектов до сих пор используют старую архитектуру, потому что обновление ломает зависимости. Поэтому мы поддерживаем оба подхода и помогаем мигрировать постепенно. Подробнее о Turbo Modules можно почитать в официальном репозитории.

Аспект Старый Bridge (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 bridging файле обязательна — без неё модуль не появится в реестре.

Пример реализации
@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) или неправильно именует метод в RCT_EXTERN_METHOD, и на JS-стороне получает undefined is not a function. Отладить сложно, потому что ошибка появляется в рантайме без стектрейса. Наш сертифицированный инженер с опытом более 100 проектов исключает такие ошибки на этапе код-ревью.

Как обеспечить потокобезопасность?

React Native вызывает методы модуля на произвольном потоке из своего пула. Если внутри метода обращаешься к UIKit — краш с UIKit called from background thread. Классическое решение — DispatchQueue.main.async { } вокруг UI-кода. Но это создаёт новую проблему: resolve/reject вызываются асинхронно, и если пользователь успел закрыть экран, completion handler обращается к уже освобождённому объекту.

Паттерн с [weak self] и guard обязателен:

DispatchQueue.main.async { [weak self] in
  guard self != nil else { return }
  resolver(result)
}

Сериализация данных. Мост принимает только типы, которые умеет сериализовать JSON: NSString, NSNumber, NSArray, NSDictionary, NSNull. Хочешь передать Data — кодируй в Base64. Хочешь передать кастомный объект — разбирай его в словарь на нативной стороне. Это особенно больно при работе с CoreBluetooth, когда нужно отдавать CBCharacteristic со всеми его свойствами.

Callbacks vs Promises vs Events. Для одноразовых результатов — Promise. Для потока событий (данные с датчика, статус подключения) — RCTEventEmitter. Смешивать подходы в одном модуле — ошибка, которая приводит к утечкам памяти: если RCTResponseSenderBlock сохранить как property и вызвать дважды, приложение крашится с Tried to call a callback that is no longer valid.

New Architecture: Turbo Modules + Codegen

Начиная с версии React Native, поддерживающей New Architecture, Codegen генерирует C++ абстракцию по TypeScript-спецификации. Файл spec выглядит так:

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');

На нативной стороне реализуем протокол NativeBiometricModuleSpec, который Codegen сгенерировал автоматически. JSI позволяет вызывать методы синхронно без сериализации в JSON — скорость принципиально другая.

Проблема: если в проекте есть хотя бы один пакет без поддержки Turbo Module, New Architecture будет работать в режиме совместимости, частично теряя преимущества. Наша команда помогает провести аудит зависимостей и спланировать миграцию без простоя.

Как разработать Native Module: пошаговый план

  1. Определить требования к нативному API и выбрать архитектуру (Old Bridge или Turbo Module).
  2. Создать Swift-класс с наследованием от NSObject и добавить @objc атрибуты.
  3. Зарегистрировать модуль в Objective-C bridging файле через RCT_EXTERN_MODULE.
  4. Реализовать методы с Promise или Events, обеспечив потокобезопасность.
  5. Написать TypeScript-типы для публичного API.
  6. Покрыть нативный код юнит-тестами (XCTest).
  7. Протестировать интеграцию с JS-слоем на симуляторе и реальном устройстве.

Подход к реализации

Аудит начинается с анализа текущей версии RN, наличия JSI-совместимых пакетов и целевого iOS-деплоймента. Если проект на версии, поддерживающей New Architecture, и команда готова — сразу пишем Turbo Module с Codegen. Если нет — классический RCTBridgeModule с прицелом на будущую миграцию.

Покрытие юнит-тестами нативной части через XCTest обязательно. Интеграционные тесты — через Detox или Jest с моком модуля на JS-стороне. Это снижает количество багов в релизе на 40%.

Документируем публичный API в TypeScript-типах, чтобы команда не лезла в нативный код каждый раз. Результат — ускорение онбординга новых разработчиков в 2 раза.

Типичная ошибка Последствие Решение
Отсутствие @objc на классе Модуль не регистрируется Добавить @objc(ModuleName)
Вызов UIKit без main.async Краш приложения DispatchQueue.main.async
Двойной вызов callback Краш приложения Использовать Promise или guard
Передача кастомного объекта Ошибка сериализации Разобрать в NSDictionary

Что входит в работу

  • Анализ требований и выбор архитектурного подхода (Old Bridge / Turbo Module)
  • Написание нативного кода на Swift с Objective-C bridging
  • TypeScript-типизация публичного API модуля
  • Обработка ошибок, потокобезопасность
  • Юнит-тесты нативной части (XCTest)
  • Интеграция с JS-слоем, проверка в симуляторе и на реальном устройстве
  • Документация по использованию модуля

Сроки

От 3 до 5 дней в зависимости от сложности нативного API, который нужно обернуть. Простая обёртка над одним системным фреймворком — ближе к 3 дням. Модуль с потоком событий, бинарными данными и поддержкой New Architecture — 5 дней и больше. Стоимость рассчитывается индивидуально после анализа требований и кодовой базы.

Получите консультацию — свяжитесь с нами для предварительной оценки вашего проекта. Закажите разработку Native Module у нас — гарантируем стабильность и производительность.

Почему нативная разработка iOS — лучший выбор для сложных приложений

Приложение крашит на cold start — EXC_BAD_ACCESS в момент инициализации синглтона, который обращается к другому синглтону, который ещё не инициализирован. Или: ViewController утечёт в памяти, потому что closure захватывает self без [weak self], и этот ViewController висит в памяти через два перехода после того, как пользователь его покинул. Это не гипотетические сценарии — это два самых частых класса проблем на iOS-проектах, которые приходят к нам после другой команды.

Мы занимаемся iOS-разработкой более 5 лет, реализовали 40+ проектов разной сложности — от стартапов до enterprise-решений с миллионами пользователей. Каждый проект проходит через 3 этапа Code Review, собственный набор UI-тестов (в среднем 150+ тест-кейсов) и обязательный прогон через Xcode Instruments до релиза.

Нативная iOS-разработка на Swift — это прямой доступ к платформе. Без прослойки, без компромиссов по производительности, с полным контролем над тем, что происходит на каждом кадре.

Почему нативная разработка iOS на Swift — выбор для enterprise-приложений?

Нативный код даёт гарантию совместимости с новыми API Apple в день их выхода, а не через месяцы адаптации в кроссплатформенных фреймворках. Для приложений с чувствительной к задержкам логикой (финансовые терминалы, медицинские мониторы, AR-навигация) это критично. Swift с ARC и строгой типизацией позволяет держать crash-free rate на уровне 99.9% при правильной архитектуре.

SwiftUI или UIKit: что выбрать для нативной разработки iOS

К настоящему времени SwiftUI покрывает подавляющее большинство production-задач. Но UIKit не устарел и не исчезнет — Apple не deprecate-ит его, а продолжает добавлять API. Реальная картина на крупных проектах: гибридный подход. SwiftUI для большинства экранов, UIKit там, где SwiftUI упирается в ограничения.

Какие сценарии SwiftUI выигрывает безоговорочно

Декларативный синтаксис SwiftUI сокращает код UI в 3–5 раз по сравнению с UIKit. Экран настроек с List, Toggle, Picker — это 40 строк SwiftUI против 200 строк UIKit с делегатами UITableViewDataSource. Экономия времени на UI-разработку достигает 60%[Apple рекомендует начинать новые проекты на SwiftUI (Human Interface Guidelines)]. При среднем бюджете проекта в 5 миллионов рублей экономия может составить до 3 миллионов рублей только на UI-слое.

@State, @Binding, @ObservableObject (а с iOS 17 — макрос @Observable) создают реактивную связь между данными и UI без ручного reloadData(). Измение @State-переменной автоматически перерисовывает затронутую часть иерархии. Это работает правильно, если понимать, как SwiftUI вычисляет diff — через Equatable и id в ForEach.

AsyncImage, NavigationStack с типобезопасным роутингом через NavigationPath, searchable, refreshable — это готовые паттерны, которые UIKit требует реализовывать вручную.

Когда UIKit остаётся необходимым

UICollectionView с compositional layout и diffable data source — сложные сетки с разными типами ячеек, горизонтальными секциями внутри вертикального скролла, динамическими размерами ячеек. SwiftUI LazyVGrid / LazyHGrid не дают такого контроля.

Кастомные переходы между экранами. UIViewControllerAnimatedTransitioning и UIViewControllerInteractiveTransitioning — интерактивный pop gesture с частичным прогрессом, кастомный hero-переход с точным управлением frame. SwiftUI matchedGeometryEffect покрывает часть случаев, но не все.

UITextView с TextKit 2. Богатый редактор текста, кастомные атрибуты, кастомный рендеринг — TextKit 2 (доступен с iOS 16) перешёл на async layout, что решило проблемы с производительностью на длинных документах. SwiftUI TextEditor — это обёртка вокруг UITextView без прямого доступа к TextKit.

UIScrollView с кастомным поведением. scrollViewDidScroll, parallax-эффекты, sticky headers с кастомной логикой, pull-to-refresh с кастомным индикатором. SwiftUI ScrollView с scrollPosition и onScrollGeometryChange (iOS 17) закрывает часть случаев, но не все.

Как мы интегрируем SwiftUI и UIKit: шаг за шагом

  1. Идентифицируем экраны, где SwiftUI даёт максимальный выигрыш (списки, формы, настройки) — обычно 70-80% экранов.
  2. Для критичных к производительности участков (сложные коллекции, кастомные анимации) оставляем UIKit.
  3. Используем UIHostingController для встраивания SwiftUI-вью в UIKit navigation stack.
  4. Для обратной совместимости оборачиваем UIKit-компоненты через UIViewRepresentable.
  5. Coordinator pattern (UIKit) управляет навигацией на уровне флоу, экраны реализованы на SwiftUI.

Один паттерн, который мы используем на проектах: UIKit-координатор управляет навигацией, а сами экраны на SwiftUI. Координатор создаёт UIHostingController, передаёт ViewModel через инициализатор или @EnvironmentObject, управляет переходами. Это даёт чистое разделение: SwiftUI занимается UI, Coordinator — навигацией.

Как async/await и Combine работают вместе

До Swift 5.5 асинхронный код на iOS строился на Combine или callback-цепочках. С появлением async/await и Actor модель конкурентности стала частью языка. На новых проектах мы используем async/await как основной инструмент для сетевых вызовов и бизнес-логики, Combine — для реактивной привязки UI-состояния.

// Правильно — @MainActor гарантирует UI-обновления на main thread
@MainActor
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false

    func loadUser(id: String) async {
        isLoading = true
        defer { isLoading = false }
        do {
            user = try await userService.fetch(id: id)
        } catch {
            // handle error
        }
    }
}

Combine остаётся незаменимым для дебаунсинга ввода, объединения нескольких Publishers (CombineLatest, Zip) и функциональной обработки потока значений (map, flatMap, filter). На практике 80% проектов используют оба подхода, выбирая инструмент под задачу.

Архитектура iOS-приложения

MVVM — базовый паттерн. ViewModel содержит логику и @Published-состояние, SwiftUI View подписывается через @ObservedObject или @StateObject. Правило одно: View не знает об URLSession, CoreData, UserDefaults.

Clean Architecture добавляет слои Repository и UseCase. UserRepository абстрагирует источник данных (сеть vs кеш). FetchUserUseCase содержит бизнес-правило. UserViewModel вызывает UseCase и управляет UI-состоянием.

TCA (The Composable Architecture) — более строгий паттерн от Point-Free. State, Action, Reducer, Effect — всё явное, всё тестируемое, composable через Scope. Хорошо работает в больших командах (5+ iOS-разработчиков), где важна предсказуемость поведения.

Что входит в разработку iOS-приложения

Этап Результаты
Анализ и проектирование Техническое задание, архитектурная схема, выбор стеков
Разработка Код с соблюдением App Store Review Guidelines, интеграция с бэкендом (REST/GraphQL)
Тестирование Unit-тесты (XCTest, покрытие >75%), UI-тесты (XCUITest, 150+ сценариев), нагрузочное тестирование через Firebase Test Lab
Публикация Оформление аккаунта разработчика, подпись кода, отправка в App Store Connect
Поддержка Гарантия 30 дней после релиза, обновления под новые версии iOS

Инструменты, без которых не обходится ни один релиз

Xcode Instruments. Time Profiler показывает, где CPU тратит время. Allocations — утечки памяти и excessive allocations. Leaks — объекты, которые не освобождаются. Перед каждым релизом — обязательный прогон.

Firebase Crashlytics. Crash-free rate, группировка по stack trace, breadcrumbs событий до крэша. Настраивается за 30 минут, даёт картину по всему парку устройств. В среднем crash-free rate на наших проектах — 99.8%.

Fastlane match. Управление сертификатами и provisioning profiles через зашифрованный git-репозиторий. Устраняет «у меня локально собирается, а на CI нет» раз и навсегда. Экономит до 4 часов на каждую сборку при ручном подписывании.

XCTest + XCUITest. Unit-тесты для ViewModel и UseCase, UI-тесты для критичных флоу (онбординг, оплата, авторизация). В среднем код покрыт на 75%.

Типичные ошибки на iOS-проектах и их решения
Проблема Решение
Утечка памяти из-за захвата self в замыкании Использовать [weak self] во всех хендлерах, где self не обязан жить дольше замыкания
Конфликты Provisioning Profiles Настроить Fastlane match и хранить сертификаты в отдельном репозитории
Медленный старт приложения из-за синхронной инициализации синглтонов Перенести инициализацию на первый вызов или использовать lazy var
Отказ App Store из-за несоответствия Section 4.2 (минимальная функциональность) Провести предварительный аудит по чек-листу App Store Review Guidelines

Процесс и сроки

Сложность Ориентировочный срок
MVP (5–8 экранов, базовый API) 6–10 недель
Среднее приложение (15–25 экранов) 3–5 месяцев
Сложное (платежи, AR, CoreML, кастомный UI) 5–9 месяцев

Стоимость рассчитывается индивидуально после анализа ТЗ и дизайна. Обычно первые 2 недели уходят на проектирование, после чего мы фиксируем сроки и бюджет. Средний бюджет на разработку среднего iOS-приложения — от 4 до 7 миллионов рублей в зависимости от сложности интеграций.

Закажите разработку под ключ — мы оценим ваш проект за 2 рабочих дня и предложим оптимальную архитектуру. Свяжитесь с нами, чтобы обсудить вашу задачу: гарантируем качество кода, соблюдение App Store Review Guidelines и опыт работы с проектами любого масштаба. Получите консультацию — мы поможем выбрать правильный стек и избежать типичных ошибок на старте.