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.
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.