Nativescript: App crash when implementing custom ios App Delegate

Created on 16 Nov 2020  ·  14Comments  ·  Source: NativeScript/NativeScript

Environment
Provide version numbers for the following components (information can be retrieved by running tns info in your project folder or by inspecting the package.json of the project):

  • CLI: 7.0.11
  • Cross-platform modules: 7.0.13
  • iOS Runtime: 7.0.5
  • XCode Version: 12.2
  • Plugin(s): none

Describe the bug
When implementing a custom app delegate in iOS, the application crashes on startup. Here the error log:

====== Assertion failed ======
Native stack trace:
1          0x101b692af tns::Assert(bool, v8::Isolate*) + 119
2          0x101b8f5a8 tns::Interop::WriteValue(v8::Local<v8::Context>, tns::TypeEncoding const*, void*, v8::Local<v8::Value>) + 3156
3          0x101b8e93c tns::Interop::SetFFIParams(v8::Local<v8::Context>, tns::TypeEncoding const*, tns::FFICall*, int, int, tns::V8Args&) + 110
4          0x101b8e5a1 tns::Interop::CallFunctionInternal(tns::MethodCall&) + 421
5          0x101b2607a tns::MetadataBuilder::CFunctionCallback(v8::FunctionCallbackInfo<v8::Value> const&) + 240
6          0x101cb613c v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) + 620
7          0x101cb55ec v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) + 556
8          0x101cb4c73 v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) + 259
9          0x10254dc19 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit + 57
JavaScript stack trace:
at run (file: node_modules/@nativescript/core/application/index.ios.js:331:0)
at <anonymous> (file:///app/bundle.js:160:64)
at ./app.ts (file:///app/bundle.js:176:30)
at __webpack_require__ (file: app/webpack/bootstrap:816:0)
at checkDeferredModules (file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback (file: app/webpack/bootstrap:30:0)
at <anonymous> (file:///app/bundle.js:2:57)
at require (VM)

To Reproduce

  • ns create test
  • select _Plain TypeScript_ and _Hello World_
  • cd test
  • Add custom empty iOS app delegate in app.ts:
    import { ios } from '@nativescript/core/application';

    class MyDelegate extends UIResponder implements UIApplicationDelegate {
        public static ObjCProtocols = [UIApplicationDelegate];

        applicationDidFinishLaunchingWithOptions(
            application: UIApplication,
            launchOptions: NSDictionary<any, any>
        ): boolean {
            console.log( "applicationWillFinishLaunchingWithOptions: " + launchOptions );

            return true;
        }

        applicationDidBecomeActive(application: UIApplication): void {
            console.log("applicationDidBecomeActive: " + application);
        }
    }
    ios.delegate = MyDelegate;
  • ns run ios

Expected behavior

Application launch without errors.

Sample project

Additional context

docs needed

Most helpful comment

Omg, love u!, that does the trick. I've been stuck with this for two days 😅

Maybe will be a good idea to update the example for custom typescript app delegate at the documentation here:

https://docs.nativescript.org/core-concepts/application-lifecycle#ios-uiapplicationdelegate

Thanks!

All 14 comments

Have you tried adding the @NativeClass decorator to the class declaration?

Omg, love u!, that does the trick. I've been stuck with this for two days 😅

Maybe will be a good idea to update the example for custom typescript app delegate at the documentation here:

https://docs.nativescript.org/core-concepts/application-lifecycle#ios-uiapplicationdelegate

Thanks!

Hey guys, I was having same issue trying to create a Delegate that was making the app to crash until I updated to NS7 just to use NativeClass decorator, but I'm having the issue that decorated classes with @NativeClass works if I place those within my index.ios.ts file, but if I try to import from an external file, it would fail:

This works:

import { GridLayout } from '@nativescript/core';

export class Test extends GridLayout {
    constructor() {
        super();
        this.init();
    }

    init() {
        if (this.nativeView) {
            console.log('Initializing Test');
            const test: TestViewController = <TestViewController> TestViewController.alloc().init();
            test.view.frame = this.nativeView.bounds;
            test.view.backgroundColor = UIColor.blackColor;
            this.nativeView.insertSubviewAtIndex(test.view, 0);
        } else {
            setTimeout(() => this.init(), 500);
        }
    }
}


@NativeClass()
export class TestViewController extends UIViewController {
    viewDidLoad() {
        console.log('A NATIVE VIEW LOADED');
    }
}

While this does not

import { GridLayout } from '@nativescript/core';
import { TestViewController } from './ios/views/test.view'; // once imported starts to fail

export class Test extends GridLayout {
    constructor() {
        super();
        this.init();
    }

    init() {
        if (this.nativeView) {
            console.log('Initializing Test');
            const test: TestViewController = <TestViewController> TestViewController.alloc().init();
            test.view.frame = this.nativeView.bounds;
            test.view.backgroundColor = UIColor.blackColor;
            this.nativeView.insertSubviewAtIndex(test.view, 0);
        } else {
            setTimeout(() => this.init(), 100);
        }
    }
}

image

Any ideas on this one?

@jonathan-casarrubias just a guess, but instead of writing:

@NativeClass 
export class TestViewController // ...

... Write:

@NativeClass 
class TestViewController // ...

export { TestViewController };

I saw a GitHub gist about this odd limitation. @farfromrefug or @NathanWalker may be able to explain.

@shirakaba ok very interesting, I've just tried and it did work!!! I believe this:

https://nativescript.org/blog/nativescript-7-for-plugin-authors/

Might be misleading, but anyways is working now.. Thanks a lot 🙌

Good point about those docs. @NathanWalker could that blog post please be edited to reflect how export should not be written inline when using @NativeClass?

Otherwise, I do believe this issue is closed!

@shirakaba i see the issue . the nativeclass transformer needs a fix to handle that case

@farfromrefug @shirakaba thanks guys, I have one more question I'm not sure I should open a new issue or not, so will ask the question here and will follow your command, if I create a new story of it is something easy..

So after migrating and doing the above ^^^ I stopped getting the errors I had, but it seems that the methods declared within the delegate class are not being executed.

Oddly if I remove the methods I get an error only for one of the methods (though many are required) so I get the following

-[RTCPeerConnectionDelegateImpl peerConnectionShouldNegotiate:]: unrecognized selector sent to instance 0x2831e8e70

If I return the class to where the methods are declared, the error disappear but is like nothing happens, I Get no logs or anything executed within the delegated methods,

@NativeClass()
class RTCPeerConnectionDelegateImpl extends NSObject implements RTCPeerConnectionDelegate {

  public static ObjCProtocols = [RTCPeerConnectionDelegate];

  delegate: WebRTCClient;

  static new(): RTCPeerConnectionDelegateImpl {
    return <RTCPeerConnectionDelegateImpl>super.new();
  }

  static initWithClientDelegate(delegate: WebRTCClient) {
    const peerDelegate = RTCPeerConnectionDelegateImpl.new()
    peerDelegate.delegate = delegate;
    return peerDelegate;
  }

  peerConnectionDidAddReceiverStreams?(peerConnection: RTCPeerConnection, rtpReceiver: RTCRtpReceiver, mediaStreams: NSArray<RTCMediaStream> | RTCMediaStream[]): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidAddStream(peerConnection: RTCPeerConnection, stream: RTCMediaStream): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeConnectionState?(peerConnection: RTCPeerConnection, newState: RTCPeerConnectionState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeIceConnectionState(peerConnection: RTCPeerConnection, newState: RTCIceConnectionState): void {
    console.log(`peerConnection new connection state: ${ newState.toString() }`);
    this.delegate.delegate.delegate.notify({
      eventName: 'iceConnStateChanged',
      object: this.delegate.delegate.delegate,
      data: newState
    })
  }
  peerConnectionDidChangeIceGatheringState(peerConnection: RTCPeerConnection, newState: RTCIceGatheringState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeSignalingState(peerConnection: RTCPeerConnection, stateChanged: RTCSignalingState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidGenerateIceCandidate(peerConnection: RTCPeerConnection, candidate: RTCIceCandidate): void {
    console.log(`Discovered local ice candidate`, candidate);
    this.delegate.delegate.delegate.notify({
      eventName: 'iceConnStateChanged',
      object: this.delegate.delegate.delegate,
      data: candidate
    })
  }
  peerConnectionDidOpenDataChannel(peerConnection: RTCPeerConnection, dataChannel: RTCDataChannel): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveIceCandidates(peerConnection: RTCPeerConnection, candidates: NSArray<RTCIceCandidate> | RTCIceCandidate[]): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveReceiver?(peerConnection: RTCPeerConnection, rtpReceiver: RTCRtpReceiver): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveStream(peerConnection: RTCPeerConnection, stream: RTCMediaStream): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidStartReceivingOnTransceiver?(peerConnection: RTCPeerConnection, transceiver: RTCRtpTransceiver): void {
    console.log("Method not implemented.");
  }
  peerConnectionShouldNegotiate(peerConnection: RTCPeerConnection): void {
    console.log("peerConnection should negotiate");
  }
}

export { RTCPeerConnectionDelegateImpl };

So none of the methods above, neither the logs are executed 😞

thoughts?

@jonathan-casarrubias For any methods that are not already exposed by the protocol you’ve specified, you need to explicitly expose them. See ObjCExposedMethods in:

https://docs.nativescript.org/core-concepts/ios-runtime/how-to/objc-subclassing#typescript-support

See also the “limitations” section. You cannot expose static methods to the native runtime. So if initWithClientDelegate is not already exposed by that protocol, and you really need it to be exposed to Obj-C, then you’re out of luck and need to write a native class instead.

... although init() methods are normally instance methods anyway (if I’m not mistaken), so I wonder if it’s correct in the first place.

@shirakaba InitWithClientDelegate is not required to be exposed I was just experimenting, so only those with the peerConnection naming, but even if I remove that and I only use something like this (which is the actual protocol)

@NativeClass()
class RTCPeerConnectionDelegateImpl extends NSObject implements RTCPeerConnectionDelegate {

  public static ObjCProtocols = [RTCPeerConnectionDelegate];

  static new(): RTCPeerConnectionDelegateImpl {
    return <RTCPeerConnectionDelegateImpl>super.new();
  }

  peerConnectionDidAddReceiverStreams?(peerConnection: RTCPeerConnection, rtpReceiver: RTCRtpReceiver, mediaStreams: NSArray<RTCMediaStream> | RTCMediaStream[]): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidAddStream(peerConnection: RTCPeerConnection, stream: RTCMediaStream): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeConnectionState?(peerConnection: RTCPeerConnection, newState: RTCPeerConnectionState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeIceConnectionState(peerConnection: RTCPeerConnection, newState: RTCIceConnectionState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeIceGatheringState(peerConnection: RTCPeerConnection, newState: RTCIceGatheringState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidChangeSignalingState(peerConnection: RTCPeerConnection, stateChanged: RTCSignalingState): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidGenerateIceCandidate(peerConnection: RTCPeerConnection, candidate: RTCIceCandidate): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidOpenDataChannel(peerConnection: RTCPeerConnection, dataChannel: RTCDataChannel): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveIceCandidates(peerConnection: RTCPeerConnection, candidates: NSArray<RTCIceCandidate> | RTCIceCandidate[]): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveReceiver?(peerConnection: RTCPeerConnection, rtpReceiver: RTCRtpReceiver): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidRemoveStream(peerConnection: RTCPeerConnection, stream: RTCMediaStream): void {
    console.log("Method not implemented.");
  }
  peerConnectionDidStartReceivingOnTransceiver?(peerConnection: RTCPeerConnection, transceiver: RTCRtpTransceiver): void {
    console.log("Method not implemented.");
  }
  peerConnectionShouldNegotiate(peerConnection: RTCPeerConnection): void {
    console.log("peerConnection should negotiate");
  }
}

export { RTCPeerConnectionDelegateImpl };

Nothing happens, I assume I should see those logs in the terminal..... The implementation looks like

    this.peerConnection = this.factory.peerConnectionWithConfigurationConstraintsDelegate(
      config,
      RTCMediaConstraints.alloc().initWithMandatoryConstraintsOptionalConstraints(null, new NSDictionary({
        objects: [kRTCMediaConstraintsValueTrue],
        forKeys: ['DtlsSrtpKeyAgreement']
      })),
      RTCPeerConnectionDelegateImpl.new()
    );

I just would like to see those callback methods executed by the GoogleWebRTC's SDK and I can easily figure out the rest, is just that I'm playing blind here 😭.

As a note, I generated the typings using the command: TNS_TYPESCRIPT_DECLARATIONS_PATH="$(pwd)/typings" tns build ios, I did not created those manually, after that I just implemented the interface, VCode asked me if I wanted to create the missing methods to be implemented, then I just added the static new() and the ObjCProtocols = [] as per instructions from the documentation and then I just pass it to the WebRTC SDK when creating a peerConnection.

So at that point there is literally 0 logic, so I had this thought that probably only the logs were not displayed in the terminal, but the methods were executed, so I blindly developed those methods with the hope that if works it works if not it should crash and give me the clue that those are actually executed.

But again, it didn't work but it didn't crash neither, so again is like those methods somehow are not executed and I see quite difficult that the official Google's WebRTC SDK is failing, as final note if I don't pass any delegate to the peerConnection factory, I do receive a peerConnection returned from that method, I can create SDP and some other stuff, but of course I couldn't finish my plugin because I need those delegates in order to receive the ICE Candidate and State Changes, but If I do pass the delegate the SDK hangs and nothing happens and when I say hangs is because the app is still up and running, is just the flow is stopped, but it does not crash the app.

So

1.- The WebRTC is actually up and running, is just not executing the delegate methods
2.- Either I try to crash the app by putting faulty code within those methods or I simply add console.log nothing happens
3.- Until this point everything is automatically generated, there is no logic in between, not manually created the protocol, and not manually implemented the protocol methods.

I apologize the long text, I'm just trying to give enough info to avoid creating the false sense that might be some custom logic in between making it fail...

Again if you guys think I should open a new ticket I'm up to whatever you guys recommend

@jonathan-casarrubias Instead of creating RTCPeerConnectionDelegateImpl.new() inline and passing it in directly as a construction param, try storing a reference to it (somewhere that won’t go out of scope – e.g. this.myDelegate = RTCPeerConnectionDelegateImpl.new()) and passing that reference in. This should keep it retained (have to be careful about strong/weak native refs even in NativeScript).

I’m not sure you need to write @NativeClass(), either; @NativeClass should suffice.

@jonathan-casarrubias As well as those steps, could you additionally try RTCPeerConnectionDelegateImpl.alloc().init() instead of RTCPeerConnectionDelegateImpl.new()?

@shirakaba I will try those right now, I'll try one by one and see if there is a difference which one made it... Will update with the results

@shirakaba ok so here are my results, hopefully helpful for anyone in the future digging in this issue..

So as a context I started by the one that made the more sense because of the diagnosis, it did sound like a lost reference so I started with that one and the sdk started working again, I still don't see any log but I'm pretty sure I still need to negotiate a connection first in order to receive those (what I was doing before having the delegate issues), but once setting a local reference this.peerConnectionDelegate = .... everything seems to correctly work again so I'm now confident that once I negotiate the connection the methods will be executed, so my results are:

1.- Once setting a local reference the SDK works as expected, at least it won't hang and the rest of the logic after works, so I still don't get logs but I think is on my side.
2.- Changing from @NativeScript() to @NativeScript and backwards does not make any difference.
3.- Changing from .new to .alloc().init() and backwards does not make any difference.

Both test 2 and 3, were tested after the sdk started working again without presenting a difference in terms of making the sdk to fail or not, it will just keep working as long as number 1 is covered.

I'll take it from here thanks a lot for your help @shirakaba 🙌

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rLoka picture rLoka  ·  3Comments

NickIliev picture NickIliev  ·  3Comments

pocesar picture pocesar  ·  3Comments

dhanalakshmitawwa picture dhanalakshmitawwa  ·  3Comments

OscarLopezArnaiz picture OscarLopezArnaiz  ·  3Comments