Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does this NodeJS Framework work with iOS Native apps using Swift ? #20

Open
umangkathuria opened this issue Jul 18, 2019 · 10 comments
Open

Comments

@umangkathuria
Copy link

Is there a Sample that I could refer to incorporate this framework in iOS App using Swift.
I am not familiar with ObjectiveC or C++. Could you please help me in getting started with Swift based native app ?

Thanks!

@jaimecbernardo
Copy link
Member

Hi @umangkathuria ,

It's been used with Swift before: The start API has been introduced by the community in this PR: JaneaSystems/nodejs-mobile#18

Unfortunately I'm not familiar enough with Swift to help you get started. Hopefully someone from the community give some input here.

@tomholub
Copy link

Here is a rough guideline to get a prototype going. You'll copy-paste some obj-c code from the sample, then create a bridging header, then start Node and use it from your Swift code.

These were copy-pasted:

//
//  NodeRunner.h
//

#ifndef NodeRunner_h
#define NodeRunner_h
#import <Foundation/Foundation.h>

@interface NodeRunner : NSObject {}
+ (void) startEngineWithArguments:(NSArray*)arguments;
@end

#endif
//
//  NodeRunner.mm
//

#include "NodeRunner.h"
#include <NodeMobile/NodeMobile.h>
#include <string>

@implementation NodeRunner

//node's libUV requires all arguments being on contiguous memory.
+ (void) startEngineWithArguments:(NSArray*)arguments
{
    int c_arguments_size=0;
    
    //Compute byte size need for all arguments in contiguous memory.
    for (id argElement in arguments)
    {
        c_arguments_size+=strlen([argElement UTF8String]);
        c_arguments_size++; // for '\0'
    }
    
    //Stores arguments in contiguous memory.
    char* args_buffer=(char*)calloc(c_arguments_size, sizeof(char));
    
    //argv to pass into node.
    char* argv[[arguments count]];
    
    //To iterate through the expected start position of each argument in args_buffer.
    char* current_args_position=args_buffer;
    
    //Argc
    int argument_count=0;
    
    //Populate the args_buffer and argv.
    for (id argElement in arguments)
    {
        const char* current_argument=[argElement UTF8String];
        
        //Copy current argument to its expected position in args_buffer
        strncpy(current_args_position, current_argument, strlen(current_argument));
        
        //Save current argument start position in argv and increment argc.
        argv[argument_count]=current_args_position;
        argument_count++;
        
        //Increment to the next argument's expected position.
        current_args_position+=strlen(current_args_position)+1;
    }
    
    //Start node, with argc and argv.
    node_start(argument_count,argv);
    free(args_buffer);
}
@end

ProjectName-Bridging-Header.h: If your project already has this file, just add a line, else add this file in your project root. Make sure in Xcode settings that this bridging file is being used during build:

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "NodeRunner.h"

Now in AppDelegate:

//
//  AppDelegate.swift
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // ... your other app code

        DispatchQueue.global(qos: .background).async {
            NodeRunner.startEngine(withArguments: ["node", "-e", "require('http').createServer((req, res) => res.end('alive!')).listen(3000);"])
        }
        
        return true
    }

  // ...
}

It would be better to rewrite the above to use NSThread, where you can give it the required 2MB stack size.

@umangkathuria
Copy link
Author

@tomholub I'll give this a try! Thanks !!

@umangkathuria
Copy link
Author

@tomholub
Thanks for the initial setup! I am able to build the app using the steps you provided in Swift.
Have you ever used this framework for calling specific functions? For example if there is a JavaScript file containing an API call that we'd like to invoke and then gather the response, how do we go about that? I dont see any documentation or methods around that. Even the startNode method just starts the node, how do we go about calling methods or passing value to javascript side through this framework?

Any help is really appreciated!!

Thanks!!

@tomholub
Copy link

You load the source file and pass it as argument during start to run your api.

                let jsFile = Bundle.main.path(forResource: "node-api.js", ofType: nil)!
                let jsFileSrc = try? String(contentsOfFile: jsFile)
                NodeRunner.startEngine(withArguments: ["node", "-e", jsFileSrc!])

Then you make http requests to the node with URLSession, Alamofire or whatever, at the port that you started the node api at. Same thing as https://code.janeasystems.com/nodejs-mobile/getting-started-ios at the bottom, just in swift.

@umangkathuria
Copy link
Author

Yes, I was able to do this. But the challenge that I see with this method is that I cannot explicitly call a method. Let's say I have 10 utility functions which take an argument and return me a value. I have implemented a solution by using events in the javascript side.
Although its working, but What I am trying to find is that is there a way which might be similar to what we have in JavaScriptCore. Something like this : "context.objectForKeyedSubscript("getData")?.call(withArguments: [])"

Where context is a JSContext Object and "getData" is the method in JavaScript that I am trying to call.

Do we have anything similar to this in the library?

Thank you for all your time and effort!! Appreciate it!

@tomholub
Copy link

You'd probably have to implement that using native node handlers, likely involving a bunch of c++, kind of like what react native is doing. This is not my domain.

@rogeriochaves
Copy link

In case anyone needs it with thread to increase stackSize like I did:

    @objc
    func startNode() {
        let jsFile = Bundle.main.path(forResource: "index.js", ofType: nil)!

        NodeRunner.startEngine(withArguments: ["node", jsFile])
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let nodejsThread = Thread(target:self, selector:#selector(startNode), object:nil)
        nodejsThread.stackSize = 2*1024*1024;
        nodejsThread.start()

        return true
    }

@wltam-myndar
Copy link

Hi guys, I ran into an issue that the swift project can only access a single .js file only, I could require() node functions like "http" and "querystring", but could not require any modules like the 'left-pad' from the sample, or a .js file under the same directory, it always return errors like below:

internal/modules/cjs/loader.js:834
throw err;
^

Error: Cannot find module 'left-pad'
Require stack:

  • /[eval]
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:831:15)
    at Function.Module._load (internal/modules/cjs/loader.js:687:27)
    at Module.require (internal/modules/cjs/loader.js:903:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at [eval]:5:15
    at Script.runInThisContext (vm.js:120:18)
    at Object.runInThisContext (vm.js:309:38)
    at Object. ([eval]-wrapper:10:26)
    at Module._compile (internal/modules/cjs/loader.js:1015:30)
    at evalScript (internal/process/execution.js:94:25) {
    code: 'MODULE_NOT_FOUND',
    requireStack: [ '/[eval]' ]
    }

I've already run the "npm install" command under the "nodejs-project" and make sure "node_modules" folder have been created. Any ideas?

@milochen0418
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants