Complex callback causes iOS to not build
What's happening?
I have a bit of a more complicated callback structure and useage of hybrid objects. The code works completely fine on android, but on iOS it doesn't compile. Provided a sample repo to test with.
I am not sure if my swift code is actually just wrong (Barely know swift), but I don't think so.
Reproduceable Code
Repo: https://github.com/ronickg/bug
import type { HybridObject } from 'react-native-nitro-modules';
export interface ComplexData {
url: string;
statusCode: number;
headers: Record<string, string>;
metadata: string[];
}
export interface MyCallback {
onSimpleEvent(message: string): void;
onComplexEvent(data: ComplexData): void;
onDataReceived(data: ComplexData, buffer: ArrayBuffer): void;
onMaybeData(data: ComplexData | undefined): void;
onMultiParam(
data: ComplexData,
buffer: ArrayBuffer,
optional: ComplexData | undefined
): void;
}
export interface CallbackTester
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {
createBuilder(callback: MyCallback): CallbackBuilder;
}
export interface CallbackBuilder
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {
setMessage(msg: string): void;
trigger(): void;
}
Swift Code:
import Foundation
import NitroModules
class HybridCallbackTester: HybridCallbackTesterSpec {
func createBuilder(callback: MyCallback) throws -> any HybridCallbackBuilderSpec {
return HybridCallbackBuilder(callback: callback)
}
}
class HybridCallbackBuilder: HybridCallbackBuilderSpec {
private let callback: MyCallback
init(callback: MyCallback) {
self.callback = callback
}
private var message: String = "default"
func setMessage(msg: String) throws {
self.message = msg
}
func trigger() throws {
// Create complex test data
let complexData = ComplexData(
url: "https://example.com/test",
statusCode: 200,
headers: ["Content-Type": "application/json", "X-Custom": "value"],
metadata: ["meta1", "meta2", "meta3"]
)
// Create test ArrayBuffer
let testString = "Test data here!"
let buffer = ArrayBuffer.allocate(size: testString.utf8.count)
testString.utf8.enumerated().forEach { (index, byte) in
buffer.data[index] = byte
}
callback.onSimpleEvent(message)
callback.onComplexEvent(complexData)
callback.onDataReceived(complexData, buffer)
callback.onMaybeData(complexData)
callback.onMultiParam(complexData, buffer, nil)
}
}
Relevant log output
Sadly the error message doesn't really help much, just shows:
Command SwiftCompile failed with a nonzero exit code
Device
iPhone 13 Pro Max
Nitro Modules Version
0.31.0
Nitrogen Version
0.31.0
Can you reproduce this issue in the Nitro Example app here?
Yes, I can reproduce the same issue in the Example app here
Additional information
- [ ] I am using Expo
- [x] I am using nitrogen
- [x] I have read and followed the Troubleshooting Guide.
- [x] I created a reproduction PR to reproduce this issue here in the nitro repo. (See Contributing for more information)
- [x] I searched for similar issues in this repository and found none.
Hi Ronald!
Sadly the error message doesn't really help much, just shows
Command SwiftCompile failed with a nonzero exit code
Please read the Troubleshooting guide - this tells you how to get the actual error message.
Reproduceable Code
Repo: https://github.com/ronickg/bug
Typically the way I fix bugs is if there is a PR to this repo here that adds the change (in your case a complex callback) to the example HybridObject (here TestObject.nitro.ts) so we can all see the build fail in this specific example app. Much easier to work with than external repos.
It's hard for me to pinpoint where exactly the issue is coming from if you have 8 different functions in your code, so please do me a favor and find out which exact function it is by removing code step by step until it builds.
I tried to narrow down the issue and make the sample as simple as possible. If one comments out the self.callback = callback the build will work again, or if one switches out the ArrayBuffer with a string type in the sepcs it works as well. More complex types like custom types or ArrayBuffers seem to have issues.
PR: https://github.com/mrousavy/nitro/pull/976
Error message:
1. Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5)
2. Compiling with effective version 5.10
3. While evaluating request IRGenRequest(IR Generation for file "/Volumes/AppleStorage/junk/nitro/packages/react-native-nitro-test/ios/HybridCallbackBuilder.swift")
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0 swift-frontend 0x0000000106fb6e24 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1 swift-frontend 0x0000000106fb4c5c llvm::sys::RunSignalHandlers() + 112
2 swift-frontend 0x0000000106fb7460 SignalHandler(int) + 360
3 libsystem_platform.dylib 0x00000001824f6744 _sigtramp + 56
4 swift-frontend 0x000000010128ef20 swift::irgen::IRGenModule::getAddrOfLLVMVariableOrGOTEquivalent(swift::irgen::LinkEntity) + 388
5 swift-frontend 0x00000001012a3a4c swift::irgen::IRGenModule::getTypeEntityReference(swift::GenericTypeDecl*) + 300
6 swift-frontend 0x00000001012a5174 swift::irgen::IRGenModule::emitTypeMetadataRecords(bool)::$_1::operator()(llvm::ArrayRef<swift::GenericTypeDecl*>, llvm::StringRef) const + 124
7 swift-frontend 0x000000010128e698 swift::irgen::IRGenModule::emitTypeMetadataRecords(bool) + 936
8 swift-frontend 0x000000010141fd9c swift::IRGenRequest::evaluate(swift::Evaluator&, swift::IRGenDescriptor) const + 4576
9 swift-frontend 0x000000010146d5c4 swift::SimpleRequest<swift::IRGenRequest, swift::GeneratedModule (swift::IRGenDescriptor), (swift::RequestFlags)17>::evaluateRequest(swift::IRGenRequest const&, swift::Evaluator&) + 180
10 swift-frontend 0x00000001014291b0 swift::IRGenRequest::OutputType swift::Evaluator::getResultUncached<swift::IRGenRequest, swift::IRGenRequest::OutputType swift::evaluateOrFatal<swift::IRGenRequest>(swift::Evaluator&, swift::IRGenRequest)::'lambda'()>(swift::IRGenRequest const&, swift::IRGenRequest::OutputType swift::evaluateOrFatal<swift::IRGenRequest>(swift::Evaluator&, swift::IRGenRequest)::'lambda'()) + 812
11 swift-frontend 0x0000000101422910 swift::performIRGeneration(swift::FileUnit*, swift::IRGenOptions const&, swift::TBDGenOptions const&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::StringRef, llvm::GlobalVariable**) + 176
12 swift-frontend 0x0000000100e0daf0 generateIR(swift::IRGenOptions const&, swift::TBDGenOptions const&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, swift::PrimarySpecificPaths const&, llvm::StringRef, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, llvm::GlobalVariable*&, llvm::ArrayRef<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>) + 156
13 swift-frontend 0x0000000100e0907c performCompileStepsPostSILGen(swift::CompilerInstance&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*) + 2108
14 swift-frontend 0x0000000100e080a8 swift::performCompileStepsPostSema(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1036
15 swift-frontend 0x0000000100e0b654 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1764
16 swift-frontend 0x0000000100e09fd8 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 3716
17 swift-frontend 0x0000000100d8e0bc swift::mainEntry(int, char const**) + 5428
18 dyld 0x000000018212dd54 start + 7184
Failed frontend command:
This is a Swift bug.
Usually I do workarounds for those kinds of things but this is a bit more tricky.
Just to be sure; it does work if you store the callback - so not the additional struct you create - but the callback directly as a property?
- private let callback: MyCallback
+ private let arrayBufferCallback: (ArrayBuffer) -> Void
(which is btw recommended anyways as some languages have overhead for extra structs)
If i have just a single callback so remove the onSimpleEvent then it works.
import type { HybridObject } from 'react-native-nitro-modules';
export interface MyCallback {
// onSimpleEvent(message: string): void;
onMaybeData(data: ArrayBuffer): void;
}
export interface CallbackTester
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {
createBuilder(callback: MyCallback): CallbackBuilder;
}
export interface CallbackBuilder
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {}
Also tried without creating an extra struct likes so, but also only works if there is a single callback:
import type { HybridObject } from 'react-native-nitro-modules';
// export interface MyCallback {
// // onSimpleEvent(message: string): void;
// onMaybeData(data: ArrayBuffer): void;
// }
export interface CallbackTester
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {
createBuilder(
callback: (message: string) => void,
onMaybeData: (data: ArrayBuffer) => void
): CallbackBuilder;
}
export interface CallbackBuilder
extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {}