Skip to main content

iOS implementation

Let's use XCode, to write iOS code. Open XCode, by running this command from the root directory of your app:

xed ios

When workspace is opened, locate Pods project and expand it. Search for Development Pods and find AppInfoPackage inside. When it's expanded, it will show all files that we created under app-info-package/ios directory.

AppInfoModuleImpl.swift

Let's start by creating a small pure Swift class that will directly retrieve all application informations:

ios/AppInfoModuleImpl.swift
import Foundation

@objc(AppInfoModuleImpl)
public class AppInfoModuleImpl : NSObject {
@objc public func getAppBuildNumber() -> String {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
}

@objc public func getAppBundleId() -> String {
return Bundle.main.bundleIdentifier!
}

@objc public func getAppVersion() -> String {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
}
}

Here, we declare AppInfoModuleImpl class, which has 3 synchronous methods.

info

To make Swift elements accessible to Objective-C world, we have to do 4 things:

  • make class extending NSObject
  • mark class and its methods (at least those methods that are meant to be exposed) as public
  • mark class with @objc(exported-objc-name) decorator
  • mark exposed methods with @objc decorator

Methods in the class are using Bundle class, for the reference, check out Apple's dedicated docs section.

We will use main Bundle instance to retrieve some information about the app.

In case of getAppBundleId method, we will use bundleIdentifier property, for other methods we use generic object(forInfoDictionaryKey:) method, where the argument is a key from Info.plist. For possible Info.plist keys, visit "About Info.plist keys and values".

AppInfoModule.h

Now, let's move to the module that will manage function calls from the JS world:

ios/AppInfoModule.h
#import <React/RCTBridgeModule.h>

/**
* When using Swift classes in ObjC header, the class must have its
* "forward declaration"
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Include-Swift-Classes-in-Objective-C-Headers-Using-Forward-Declarations
*/
@class AppInfoModuleImpl;

/**
* Declare the ObjC interface for that native module class.
*
* It must extend NSObject (like every class in ObjC) and
* implement RCTBridgeModule (like each RN native module).
*/
@interface AppInfoModule : NSObject<RCTBridgeModule>

@end

AppInfoModule class extends base NSObject class (each class in Objective-C needs to extend NSObject) and implements RCTBridgeModule protocol (each RN iOS module needs to implement RCTBridgeModule protocol).

Additionally, to use Swift AppInfoModuleImpl class, we must do "forward-declaration" (check out Apple's Swift-ObjC interop dedicated docs section).

AppInfoModule.mm

ios/AppInfoModule.mm
#import "AppInfoModule.h"

/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "AppInfoPackage-Swift.h"

#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"

/**
* Each turbo module implements codegenerated spec protocol
*/
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif

/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule

/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)

#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif

@end

This is the implementation file for AppInfoModule class.

Before declaring the implementation block, we have to import Swift-ObjC generated interface header (check out dedicated docs section), and we have to implement code-generated spec protocol for new architecture (code between #if RCT_NEW_ARCH_ENABLED & #endif directives).

After that, inside implementation block we have RCT_EXPORT_MODULE macro invoked with AppInfoModule argument. This basically registers the module with provided name on iOS side, so that it's accessible in the JS world.

Also, for new architecture, it implements - getTurboModule: method (from RCTTurboModule protocol). If you won't implement that method, XCode will complain that the code-generated spec protocol methods are not implemented.

Let's use AppInfoModuleImpl class

ios/AppInfoModule.mm
// ...

/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}

- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}

/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)

/**
* Declare if module should be initialized on the main queue
*/
+ (BOOL)requiresMainQueueSetup
{
return NO;
}

// ...

@end

First, we declare private variable on AppInfoModule which will be initialized in the - init method. We can use AppInfoModuleImpl Swift class, because it's imported from AppInfoPackage-Swift.h.

Next, we override static method + requiresMainQueueSetup, which must be done whenever - init method on the module class is overriden.

Finally, we can register exported methods with RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD macro and return value from AppInfoModuleImpl class:

ios/AppInfoModule.mm
// ...

/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}

// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}

// ...

@end
Complete AppInfoModule.mm file
#import "AppInfoModule.h"

/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "AppInfoPackage-Swift.h"

#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"

/**
* Each turbo module implements codegenerated spec protocol
*/
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif

/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}

- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}

/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)

/**
* Declare if module should be initialized on the main queue
*/
+ (BOOL)requiresMainQueueSetup
{
return NO;
}

// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}

#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif

@end

You can check training repo for Objective-C & Swift implementation here and Objective-C-only implementation here.

That's iOS part, now let's go to Android!