feat: Provide API to release the native object wrapped by a JS one (#6873)

* Add `releaseNativeObject` function in `utils`
* Add tests
* Add typings for the global `__releaseNativeCounterpart` functions
provided by Android and iOS runtimes

refs https://github.com/NativeScript/ios-runtime/issues/1062 and https://github.com/NativeScript/android-runtime/issues/1254
This commit is contained in:
Martin Bektchiev
2019-02-08 10:42:51 +02:00
committed by Svetoslav
parent 9f8d24a811
commit 8b4a9b3c6b
10 changed files with 111 additions and 17 deletions

View File

@@ -7,7 +7,7 @@
4. tests should use TKUnit.assert(condition, message) to mark error. If no assert fails test is successful
5. (if exists) at the end of each test tearDown() module function is called
6. (if exists) at the end of module test tearDownModule() module function is called
*/
import * as Application from "tns-core-modules/application";
@@ -317,6 +317,12 @@ export function assertAreClose(actual: number, expected: number, delta: number,
}
}
export function assertMatches(actual: string, expected: RegExp, message?: string) {
if (expected.test(actual) !== true) {
throw new Error(`"${actual}" doesn't match "${expected}". ${message}`);
}
}
export function arrayAssert(actual: Array<any>, expected: Array<any>, message?: string) {
if (actual.length !== expected.length) {
throw new Error(message + " Actual array length: " + actual.length + " Expected array length: " + expected.length);
@@ -330,6 +336,11 @@ export function arrayAssert(actual: Array<any>, expected: Array<any>, message?:
}
export function assertThrows(testFunc: () => void, assertMessage?: string, expectedMessage?: string) {
const re = expectedMessage ? new RegExp(`^${expectedMessage}$`) : null;
return assertThrowsRegExp(testFunc, assertMessage, re);
}
export function assertThrowsRegExp(testFunc: () => void, assertMessage?: string, expectedMessage?: RegExp) {
let actualError: Error;
try {
testFunc();
@@ -341,8 +352,8 @@ export function assertThrows(testFunc: () => void, assertMessage?: string, expec
throw new Error("Missing expected exception. " + assertMessage);
}
if (expectedMessage && actualError.message !== expectedMessage) {
throw new Error("Got unwanted exception. Actual error: " + actualError.message + " Expected error: " + expectedMessage);
if (expectedMessage && !expectedMessage.test(actualError.message)) {
throw new Error("Got unwanted exception. Actual error: " + actualError.message + " Expected to match: " + expectedMessage);
}
}
@@ -455,4 +466,4 @@ function doModalAndroid(quitLoop: () => boolean, timeoutSec: number, shouldThrow
quit = true;
}
}
}
}

View File

@@ -264,6 +264,9 @@ allTests["RESET-ROOT-VIEW"] = resetRootViewTests;
import * as rootViewTests from "./ui/root-view/root-view-tests";
allTests["ROOT-VIEW"] = rootViewTests;
import * as utilsTests from "./utils/utils-tests";
allTests["UTILS"] = utilsTests;
const testsSuitesWithLongDelay = {
HTTP: 15 * 1000,
}
@@ -504,4 +507,4 @@ class TestInfo implements TKUnit.TestInfoEntry {
this.testTimeout = testTimeout;
this.duration = duration;
}
}
}

View File

@@ -0,0 +1,44 @@
import * as TKUnit from "../TKUnit";
import * as utils from "tns-core-modules/utils/utils";
import { isIOS } from "tns-core-modules/platform";
export function test_GC_isDefined() {
TKUnit.assertNotEqual(utils.GC, undefined, "Method utils.GC() should be defined!");
};
export function test_releaseNativeObject_isDefined() {
TKUnit.assertNotEqual(utils.releaseNativeObject, undefined, "Method utils.releaseNativeObject() should be defined!");
};
export function test_releaseNativeObject_canBeCalledWithNativeObject() {
if (isIOS) {
test_releaseNativeObject_canBeCalledWithNativeObject_iOS();
} else {
test_releaseNativeObject_canBeCalledWithNativeObject_Android();
}
};
function test_releaseNativeObject_canBeCalledWithNativeObject_iOS() {
let deallocated = false;
const obj = new ((<any>NSObject).extend({
dealloc: function () {
deallocated = true;
}
}));
TKUnit.assertMatches(obj.description, /NSObject/, "Object description should match!")
utils.releaseNativeObject(obj);
// Need to sleep to make the delayed release get executed
NSThread.sleepForTimeInterval(0);
TKUnit.assertTrue(deallocated, "NativeObject must have been deallocated!");
}
function test_releaseNativeObject_canBeCalledWithNativeObject_Android() {
const obj = new java.lang.Object();
TKUnit.assertMatches(obj.toString(), /java.lang.Object/, "Object description should match!")
utils.releaseNativeObject(obj);
TKUnit.assertThrowsRegExp(obj.toString.bind(obj), "Should throw an error!", /Failed calling toString on a java\/lang\/Object instance/);
}

View File

@@ -206,6 +206,10 @@ export function GC() {
gc();
}
export function releaseNativeObject(object: java.lang.Object) {
__releaseNativeCounterpart(object);
}
export function openUrl(location: string): boolean {
const context = ad.getApplicationContext();
try {

View File

@@ -191,7 +191,7 @@ export module ios {
*/
export function getter<T>(_this: any, propertyValue: T | {(): T}): T;
// Common properties between UILabel, UITextView and UITextField
// Common properties between UILabel, UITextView and UITextField
export interface TextUIView {
font: any;
textAlignment: number;
@@ -261,6 +261,12 @@ export module ios {
*/
export function GC();
/**
* Releases the reference to the wrapped native object
* @param object The Java/Objective-C object to release.
*/
export function releaseNativeObject(object: any /*java.lang.Object | NSObject*/);
/**
* Returns true if the specified path points to a resource or local file.
* @param path The path.
@@ -281,13 +287,13 @@ export function openUrl(url: string): boolean
/**
* Escapes special regex symbols (., *, ^, $ and so on) in string in order to create a valid regex from it.
* @param source The original value.
* @param source The original value.
*/
export function escapeRegexSymbols(source: string): string
/**
* Converts string value to number or boolean.
* @param value The original value.
* @param value The original value.
*/
export function convertString(value: any): any
@@ -308,4 +314,4 @@ export function hasDuplicates(arr: Array<any>): boolean;
* Removes duplicate elements from array.
* @param arr - The array.
*/
export function eliminateDuplicates(arr: Array<any>): Array<any>;
export function eliminateDuplicates(arr: Array<any>): Array<any>;

View File

@@ -105,7 +105,7 @@ export module ios {
// Strip part after tns_modules to obtain app root
appPath = currentDir.substring(0, tnsModulesIndex);
}
return appPath;
}
@@ -140,6 +140,10 @@ export function GC() {
__collect();
}
export function releaseNativeObject(object: NSObject) {
__releaseNativeCounterpart(object);
}
export function openUrl(location: string): boolean {
try {
var url = NSURL.URLWithString(location.trim());
@@ -175,4 +179,4 @@ class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UI
}
}
mainScreenScale = ios.getter(UIScreen, UIScreen.mainScreen).scale;
mainScreenScale = ios.getter(UIScreen, UIScreen.mainScreen).scale;

View File

@@ -3,10 +3,16 @@
declare function float(num: number): any;
declare function long(num: number): any;
/**
* Triggers garbage collection in JavaScript
*/
declare var gc: () => void;
declare function float(num: number): any;
declare function long(num: number): any;
/**
* Releases the reference to the wrapped native object
* @param object The Java object to release.
*/
declare function __releaseNativeCounterpart(object: java.lang.Object): void;
interface ArrayConstructor {
create(type: any, count: number): any;

View File

@@ -1,4 +1,4 @@
/// <reference path="interop.d.ts" />
/// <reference path="runtime.d.ts" />
/// <reference path="objc-x86_64/objc!ARKit.d.ts" />
/// <reference path="objc-x86_64/objc!AVFoundation.d.ts" />
/// <reference path="objc-x86_64/objc!AVKit.d.ts" />
@@ -126,4 +126,3 @@
/// <reference path="objc-x86_64/objc!os_object.d.ts" />
/// <reference path="objc-x86_64/objc!simd.d.ts" />
/// <reference path="objc-x86_64/objc!zlib.d.ts" />
declare function __collect(): void;

View File

@@ -0,0 +1,17 @@
/// <reference path="interop.d.ts" />
/**
* Triggers garbage collection in JavaScript
*/
declare function __collect(): void;
/**
* Releases the reference to the wrapped native object
* @param object The Objective-C object to release.
*/
declare function __releaseNativeCounterpart(object: NSObject): void;
/**
* Gets accurate system timestamp in ms.
*/
declare function __time(): Number;

View File

@@ -42,11 +42,11 @@ mv ios-typings-prj/typings/x86_64/* ios/objc-x86_64/
echo "Emitting (ios/ios.d.ts)..."
pushd ios
echo '/// <reference path="interop.d.ts" />' > ios.d.ts
echo '/// <reference path="runtime.d.ts" />' > ios.d.ts
for i in `ls objc-x86_64/*.d.ts`; do
echo "/// <reference path=\"$i\" />" >> ios.d.ts
done
echo 'declare function __collect(): void;' >> ios.d.ts
popd