skip to content

In React Native on iOS, why must a Turbo Module's implementation file be Objective-C++ (.mm) rather than plain Objective-C (.m)?

level: juniorimportance: should knowfreq 30%

answer

  1. half of the generated header is C++
  2. getTurboModule returns a shared_ptr
  3. SpecJSI derives from ObjCTurboModule
  4. typed object params become JS:: structs
  5. the file extension picks the compiler mode

basics

~20 s

The Codegen spec is partly C++: getTurboModule: must return a std::shared_ptr to a generated C++ JSI class, and typed object parameters arrive as C++ structs. Only an Objective-C++ (.mm) file compiles C++ and Objective-C together.

solid answer

~30 s

For a spec such as `NativeSmartLock.ts`, Codegen generates an Objective-C protocol `NativeSmartLockSpec` **and** a C++ class `facebook::react::NativeSmartLockSpecJSI` that derives from `ObjCTurboModule`. Your class must implement `getTurboModule:`, which takes a `const ObjCTurboModule::InitParams &` and returns `std::make_shared<NativeSmartLockSpecJSI>(params)` — namespaces, templates and smart pointers that an Objective-C compiler cannot parse. Typed object parameters also arrive as C++ structs in a `JS::` namespace. Clang chooses the language from the extension, so the Xcode template's `.m` file must be renamed to `.mm`. Business logic can still live in Swift behind that class, but the class React Native talks to is Objective-C++.

code

objective-cpp · 18 lines
objective-cpp
// RCTSmartLock.mm — compiled as Objective-C++
#import "RCTSmartLock.h" // imports <SmartLockSpec/SmartLockSpec.h>

@implementation RCTSmartLock

+ (NSString *)moduleName
{
  return @"NativeSmartLock";
}

// C++ in, C++ out: this method alone rules out a .m file
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return std::make_shared<facebook::react::NativeSmartLockSpecJSI>(params);
}

@end

go deeper

for a junior

Remember that the generated spec mixes an Objective-C protocol with a C++ JSI class, and that a .mm extension is what lets one file hold both.

for a middle

Point to getTurboModule:, its InitParams argument, std::make_shared and the JS:: structs as the concrete C++ pieces, and explain that the extension selects the compiler mode.

for a senior

Explain how the C++ requirement propagates through headers, why that keeps the spec out of Swift bridging headers, and how you keep the Objective-C++ layer thin.

for a principal

Frame the .mm layer as the fixed cost of Swift-C++ interop and weigh where a team puts its logic: Objective-C++, Swift behind an adapter, or a shared C++ module.

## The short answer A **Turbo Module** is a native module that JavaScript calls through **JSI**, the C++ interface between the JavaScript engine and native code. On iOS the class you write sits exactly on the seam between two worlds: the Objective-C world of Foundation types and blocks, and the C++ world of React Native's core. **Objective-C++** — Objective-C source compiled with C++ enabled, signalled by the `.mm` extension — is the one language that can hold both in a single file. A plain `.m` file is compiled as Objective-C only, so the moment it imports the generated spec header, the build fails. ## What Codegen generates for an iOS module **Codegen** reads your typed JavaScript spec (for example `NativeSmartLock.ts`) and, on iOS, runs when you `pod install`. For a library whose `codegenConfig.name` is `SmartLockSpec`, it writes a header named `SmartLockSpec/SmartLockSpec.h` containing: - **An Objective-C protocol**, `NativeSmartLockSpec`, that extends `RCTBridgeModule` and `RCTTurboModule` and declares one selector per spec method. - **A C++ class**, `facebook::react::NativeSmartLockSpecJSI`, deriving from `ObjCTurboModule`. This is the JSI host object JavaScript actually holds; it forwards each call to your Objective-C instance. - **C++ structs** in a `JS::` namespace for typed object parameters, so a method that takes an object alias receives a C++ reference such as `JS::NativeSmartLock::UnlockOptions &` rather than an `NSDictionary`. The protocol is plain Objective-C, but the header that declares it also declares C++ types. Any file that imports it must therefore be compiled as Objective-C++. ## Where C++ enters your class | Piece of the glue | Language | Why it matters | |---|---|---| | `getTurboModule:` return type | C++ | `std::shared_ptr<facebook::react::TurboModule>` | | Its parameter | C++ | `const facebook::react::ObjCTurboModule::InitParams &` | | Its body | C++ | `std::make_shared<facebook::react::NativeSmartLockSpecJSI>(params)` | | Typed object parameters | C++ | `JS::` structs with generated accessors | | Promise parameters | Objective-C | `RCTPromiseResolveBlock` and `RCTPromiseRejectBlock` blocks | | `+moduleName` | Objective-C | returns the JavaScript-facing name as an `NSString` | Most of the file is ordinary Objective-C; it is the **factory method** and the **structs** that pull in C++. The factory is how React Native obtains the JSI object for your instance: it hands you `InitParams` (the module name, your instance, the JavaScript call invoker) and expects the generated C++ wrapper back. ## .m, .mm and .swift compared | File | Compiled as | Can it implement the spec? | |---|---|---| | `.m` | Objective-C | No — the generated header's C++ declarations do not parse | | `.mm` | Objective-C++ | Yes — this is the documented shape | | `.swift` | Swift | Not directly; React Native's docs keep a thin `.mm` class and forward to Swift through an adapter | React Native's own guide for Swift modules says it plainly: the core is mainly C++, Swift and C++ interoperate poorly, so some Objective-C++ glue is unavoidable and the goal is to keep it thin. ## What this means in practice 1. **Rename the file.** Xcode's Cocoa Touch Class template creates `RCTSmartLock.m`; the official tutorial's last setup step is renaming it to `RCTSmartLock.mm`. 2. **Keep the spec import out of Swift.** The class header imports the generated spec header, which contains C++, so that header belongs to Objective-C++ files and is not something to expose to Swift through a bridging header. 3. **Put logic wherever it reads best.** The `.mm` class can call Foundation APIs directly, or hold a Swift object and forward to it; either way it is the class the runtime instantiates. 4. **Re-run `pod install` after changing the spec**, so Codegen regenerates the protocol and the JSI class your `.mm` compiles against. ## Mistakes interviewers listen for - Saying the `.mm` extension is a naming convention with no effect — it switches the compiler into Objective-C++ mode. - Believing Swift can adopt the generated protocol directly because it is "just a protocol" — the header that declares it is C++-laden. - Thinking the whole module must be written in C++ — only the factory method and the structs are C++; the rest is ordinary Objective-C. - Confusing this with a pure C++ Turbo Module — there the logic is a cross-platform C++ class, and the iOS side shrinks to a small Objective-C++ provider whose `getTurboModule:` constructs it. - Expecting Objective-C++ to change how blocks, ARC or Foundation behave — it is a superset, so the Objective-C half of the file works exactly as before.

  • Which file does the C++ requirement leak into besides the implementation?
    The class header, because it imports the generated spec header to adopt `NativeSmartLockSpec`, and that header declares the C++ `NativeSmartLockSpecJSI` class and `JS::` structs. So every file importing the class header must itself be Objective-C++; it cannot be pulled into a Swift bridging header.
  • Does renaming to .mm change how the Objective-C parts behave?
    No. Objective-C++ is a superset of Objective-C, so selectors, blocks, ARC and Foundation types behave the same. The rename only lets the same file also contain C++ types such as `std::shared_ptr` and the generated `JS::` structs.

saying these in an interview costs you the question

  • The .mm extension is only a naming convention with no effect on compilation
  • Swift can adopt the generated spec protocol directly, it is just a protocol
  • The entire Turbo Module must be written in C++
  • A .m file works if you forward-declare the SpecJSI class
  • Objective-C++ changes how blocks and ARC behave in the file