Tag Archives: iOS

NativeScript iOS Delegates and easy access to modifications.

UPDATE: After a lot of direct testing; this technique does NOT work for fixing and/or updating iOS delegates.   NativeScript does NOT follow the JavaScript specs in this area.  (in other words NativeScript is broken/buggy here, but I do not expect to see a fix for it.)  So even though the JS engine updates the code properly and the delegate object on the JS side of the bridge looks like it is updated.  The original delegate that was first created is cached and used from that point forward.   This technique is valid for any other normal JavaScript stuff; but does not work for the iOS NativeScript delegates as I had expected it to.   Sorry for the bad information, everything would/should have worked if NativeScript didn't introduce bugs into the JS engine in this area on iOS.

---

Over a year ago, I had a long discussion with the NativeScript team about opening the delegates to allow the developers to easily access and change them before they are created.   Unfortunately a couple people on the NativeScript team disagreed with me and my reasoning and rejected my pull request that would have allowed easily extending the delegates before they are created.    They unfortunately thought it was "unsafe", and that was that, the delegate doors were shut.

So then for any projects I needed to extend a delegate I pretty much had to use a custom version of the NativeScript Core Modules for my clients. This has bit me a couple times; when I upgraded as then my changes were wiped out.  (Tip: Always list any of these type changes in your project readme, with code!)

Also, I never had the time to finish my planned work around "TNS-Core-Patcher"; which would have allowed plugins to have patch files to patch the NativeScript core modules during the prepare phase and include any code like delegate changes.     The TCP project is only partially done, so this post isn't about that cool project, that still might see the light of day when I have that mythical spare time.

But instead it is about a post about a issue that Gheric posted to the ios-runtime repo yesterday.   I originally responded and said my usual spiel about about how it only works if you can get access to the delegates prototype chain and that the NS team had dismissed allowing it on this issue.    He responded back and said he didn't see anything in my examples that couldn't be done using his proposed feature.

At first I was thinking, he had no idea what he was talking about.   But I am so happily wrong!    The current version of NativeScript uses this._delegate assignment inside the class for almost every single instantiated delegate that I can see.  I just quickly searched through the code and as far as I can tell; I can get access to every single created delegate.  This is what I believe Gheric was pointing out that the underlying instantiated delegate is accessible.  If he wasn't pointing to it; then because of his persistence; he made me go look at it again and then I discovered it.  But either way; it was because of Gheric's post that I understood the ramifications of having access to the instantiated delegates.

So lets add to to that, NativeScript's current engines now support a large chunk of ES6, not full ES6, but a large enough chunk where what I need exists now.

So what does that give me?   Well, it just gave me the entire ios delegate world!!!   Delegates are no longer locked behind an artificial barrier, and I can change them however and whenever I want!!!  Can you tell I'm excited?    I just got a second Christmas!

I now have a technique that totally eliminates the stupid door the team closed. So instead of letting me have a easy door to make my changes; I have found a awesome open window to allow me to make the exact same changes.

(c) 2013,Angelo DeSantis - https://www.flickr.com/photos/angeloangelo/8607844680/

Ok, so lets get to the nuts and bolts of how to do this.  The key is the ES6 Object.getPrototypeOf()function.  This allows us to get the prototype of an already instantiated object.   So it is almost as good as my original pull request.  In my original pull request I would be able to

let x = require('someComponent');
x.exportedDelegate.prototype.addFunction = function() {
/* Do whatever iOS expects this delegate function to do */
}

Very simple, and allowed me access to all sorts of things iOS expects you to be able to do when you want certain customizations.   Doing it off the prototype chain of the exported delegate is the easiest and BEST place to do it.   It doesn't matter if the delegate is instantiated or not.

Now the WORKING technique is:

let originalPrototype = Object.getPrototypeOf(someClass._delegate);
originalPrototype.addFunction = function() {
/* do whatever iOS expects this function to do */
}

Not that much harder, and the end result is identical.   However there is one potential gotcha with this new technique; if the delegate has not been instantiated yet -- you obviously can't get access to its prototype chain.  Depending on which delegate you need to modify you might have to wrap the code in a looping setTimeout() until the delegate is instantiated.

function fixPrototype(classObject, delegateName, delegateFunctionName, delegateFunction) {
if (!classObject[delegateName]) {
setTimeout(() => { fixPrototype(classObject, delegateName, delegateFunctionName, delegateFunction, 100);
return;
}
let prototypeOf = Object.getPrototypeOf(classObject[delegateName]);
protoypeOf[delegateFunctionName] = delegateFunction;
}

/* Call it */
fixPrototype(someClass, "_delegate", "addFunction", () => { /* do whatever ios expect */ });

 

This simple function, will allow you to pass in the object class, then the delegate name (most the time "_delegate" and then the function name you want to add, and then the function itself.   It takes care of the rest.

Remember, once you have patched a delegate prototype; ALL delegates using that prototype (including any of them created in the future) will get your changes.  That is why it is so important to patch the delegate prototype, you only need to do it once and it works everywhere.

NativeScript and Console logging on iOS (including XCode 9)

Been a while since I posted; and most this info was going to be presented at the NativeScript Developer days speech that we ran out of time in our cool session.     So I'm going to present it here, because I think this will help a lot of people.

First of all, NativeScript has an issue (at least on my machine) where the logging is incomplete and missing important things, like crash reports (and now on XCode 9, non-existent).

Well, when I ran into the two first issues using XCode 8.2 & 8.3.   I figured out a work around, because I hate my tools not working.    🙂

There is a really awesome set of utilities called libimobiledevice this set of utilities lets you do all sorts of things with a real iphone when it is connected to your computer including reading the logs.

 

Real Devices (any version of iOS)

So I typically do idevicesyslog | grep CONSOLE and it will then show me all my logs for the device and it basically fixes any issues other than some exception reporting.   Most the time my app doesn't crash so that command above I use probably 99% of the time.    However in the cases I need to see the NativeScript actual crash logs.   I use the above command and it will spit out the PID of your app next to the CONSOLE word.  I then cancel it, and do a idevicesyslog | grep <PIDID> and this gives me the full log including anything the NativeScript runtimes print.

Please note this only works for real devices.

Simulators (iOS 10 and before)

UPDATE for iOS 11 simulators, please see below!

Now for simulators; the process is very similar.

You run xcrun simctl list | grep Booted and it will give you a line like this:
iPhone X (4701B6F6-0EE4-423F-B5E2-DE1B5A8C32AC) (Booted)

Do you see that big old long uuid; you need that.

tail -f ~/Library/Logs/CoreSimulator/4701B6F6-0EE4-423F-B5E2-DE1B5A8C32AC/system.log | grep CONSOLE

This allows you to grep the log from that specific simulator.   Again, using the grep CONSOLE filter you can limit it to any console logs.   And you can also use grep <PIDID> to filter it to any application if you need the filtered down to, if you are needing the specific NativeScript application level logging or crash reports too.

 

Simulators (iOS 11)

iOS 11, changed the logging for the Simulator drastically.  Technically iOS 10 changes the NSLog drastically, but 11 enforced some new rules.   So iOS 11 broke the NativeScript internal logging; but it broke the cool tail trick above.    Everything now is now running through the new os_log facility.  The easiest way to get logging from your simulator is this single line:

log stream --level debug --predicate 'senderImagePath contains "NativeScript"'
--style syslog

Please note this is case sensitive, if you don't case the senderImagePath and NativeScript you won't have your filtered logging (or any logging at all if you type it wrong).   This part of it filters it down to just any NativeScript logging from all emulators running.

I created a simple bash script called /usr/bin/local/tnslog and dropped the above line it in and then chmod +x /usr/local/bin/tnslog 'd it.  So I can easily just start tns, and then open another terminal tab; and type tnslog and have all my logging again.

 

Installation

The simulator you need no extra tools.   For real devices you need the libimobiledevice toolset, which you can easily install via brew with the following two commands:

brew install --HEAD usbmuxd

and

brew install --HEAD libimobiledevice

It is really important that you install from head, the changes in Xcode 9 and iOS 11 have made a couple changes to both those libraries require some updates and the normal brew packages are out of date and doesn't have it.  So you need the latest and greatest.

 

Final Thoughts

I have been lazy, and just documented the steps.   If someone has some spare time and wants to create a quick bash script to automate the grabbing of the simulator id, and starting the older logging; against that simulator that would be cool to add to our tools.  But since I'm using primarily iOS 11 emulators now; my tnslog script handles all logging until the NativeScript team can fix their tool's issues.