mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
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:
committed by
Svetoslav
parent
9f8d24a811
commit
8b4a9b3c6b
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
tests/app/utils/utils-tests.ts
Normal file
44
tests/app/utils/utils-tests.ts
Normal 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/);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
14
tns-core-modules/utils/utils.d.ts
vendored
14
tns-core-modules/utils/utils.d.ts
vendored
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
3
tns-platform-declarations/ios/ios.d.ts
vendored
3
tns-platform-declarations/ios/ios.d.ts
vendored
@@ -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;
|
||||
|
||||
17
tns-platform-declarations/ios/runtime.d.ts
vendored
Normal file
17
tns-platform-declarations/ios/runtime.d.ts
vendored
Normal 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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user