question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RCT_EXTERN_MODULE Swift modules broken in Xcode 10.2

See original GitHub issue

🐛 Bug Report

It appears Obj-C runtime got more strict with Swift ABI stability in Xcode 10.2 and now causes a crash at runtime when trying to load modules that have Swift implementations. This is the crash error:

Swift class extensions and categories on Swift classes are not allowed to have +load methods

To Reproduce

Create a RN NativeModule e.g.

BadgeHandler.m

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(BadgeHandler, NSObject)
    RCT_EXTERN_METHOD(setBadgeNumber:(int *)badgeNumber)
@end

BadgeHandler.swift

import UIKit

@objc(BadgeHandler)
class BadgeHandler: NSObject, RCTBridgeModule {
    static func moduleName() -> String! {
        return "BadgeHandler";
    }

    static func requiresMainQueueSetup() -> Bool {
        return true
    }
    
    @objc func setBadgeNumber(_ badgeNumber: Int) {
        DispatchQueue.main.async {
            UIApplication.shared.applicationIconBadgeNumber = badgeNumber
        }
    }
}

AppName-Bridging-Header.h

#ifndef AppName_Bridging_Header_h
#define AppName_Bridging_Header_h

#import <React/RCTBridgeModule.h>

#endif /* AppName_Bridging_Header_h */

Expected Behavior

The NativeModule would load fine at runtime like it did in previous versions of Xcode

Code Example

See above ^

Environment

  React Native Environment Info:
    System:
      OS: macOS 10.14.3
      CPU: (12) x64 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
      Memory: 31.36 MB / 32.00 GB
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 11.6.0 - /usr/local/bin/node
      Yarn: 1.13.0 - /usr/local/bin/yarn
      npm: 6.5.0 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.2
      Android SDK:
        API Levels: 23, 25, 26, 27, 28
        Build Tools: 27.0.3, 28.0.2, 28.0.3
        System Images: android-28 | Google APIs Intel x86 Atom
    IDEs:
      Xcode: 10.2/10E125 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.8.3 => 16.8.3
      react-native: 0.59.1 => 0.59.1

Other

This seems to point to this macro in RCTBridgeModule.h:

https://github.com/facebook/react-native/blob/d9eae2a8093156c8faae0c5ae14546bbc95e7a3d/React/Base/RCTBridgeModule.h#L71-L74

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:78
  • Comments:65 (7 by maintainers)

github_iconTop GitHub Comments

52reactions
zienagcommented, Mar 26, 2019

Hi! I’m not very familiar with react-native, but I found myself here because I’m blocked from upgrading to 10.2 by one of dependency that uses react-native.

I have an idea for a fix though:

#define RCT_EXPORT_MODULE(js_name, objc_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
__attribute__((constructor)) \
static void RCT_CONCAT(initialize_, objc_name)() { RCTRegisterModule([objc_name class]); }

The problem with this is that RCT_EXPORT_MODULE macro will require objc_name parameter now. As I understand, this is not very convenient, as it is used in many places by users. So another solution is to introduce another macro:

#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
__attribute__((constructor)) \
static void RCT_CONCAT(initialize_, objc_name)() { RCTRegisterModule([objc_name class]); }

And replace call in RCT_EXTERN_REMAP_MODULE to new macro:

#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
  objc_name : objc_supername \
  @end \
  @interface objc_name (RCTExternModule) <RCTBridgeModule> \
  @end \
  @implementation objc_name (RCTExternModule) \
  RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name)

I’m ok to make a pr, but it will take some time until I figure out how to setup everything.

12reactions
davethomas11commented, Mar 27, 2019

If you can’t update your react-native version to get this fix when it is merged because you are using the Expo fork of react-native. Then add this code ( based on above PR ) to a header file:

/**
 * Same as RCT_EXPORT_MODULE, but uses __attribute__((constructor)) for module
 * registration. Useful for registering swift classes that forbids use of load
 * Used in RCT_EXTERN_REMAP_MODULE
 */
#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
__attribute__((constructor)) static void \
RCT_CONCAT(initialize_, objc_name)() { RCTRegisterModule([objc_name class]); }

#define RCT_EXTERN_MODULE_2(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE_2(, objc_name, objc_supername)

/**
 * Like RCT_EXTERN_MODULE, but allows setting a custom JavaScript name.
 */
#define RCT_EXTERN_REMAP_MODULE_2(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name)

Then in your module .m objective C file import that header file you just created and use RCT_EXTERN_MODULE_2 instead of RCT_EXTERN_MODULE

Example:

#import "RctExternModule2HeaderFileThatYouCreatedReplaceFilenameHere.h"
#import "React/RCTViewManager.h"

@interface RCT_EXTERN_MODULE_2(SomeModuleNameOfYoursHere, RCTViewManager)
@end
Read more comments on GitHub >

github_iconTop Results From Across the Web

Failed to build module, Apple Swift version 5.1.3 have features ...
This error indicates that you have a binary named SciChart that was built with a previous version of the Swift compiler (5.1.3). Your...
Read more >
After clean build in xcode getting no such module on all imports
My code was running fine, but after clean build (cmd+shift+k) all of sudden i started getting no such module found on all my...
Read more >
Enabling Module Stability in Swift Package Manager Projects
To compile with module stability outside of Xcode, we need to pass the following flags: -emit-module-interface; -enable-library-evolution. For ...
Read more >
How to update to Xcode 10.2.1 on High Sierra - CodeWithChris
4), this means that developers who have old Macs won't be able to update their OS and develop in Swift 5 using Xcode...
Read more >
Modularize Xcode Projects using local Swift Packages - ITNEXT
Swift Package Manager is most likely the future of working with Swift dependencies, but we can also use it to manage local code!...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found