Skip to content

Commit ccfe268

Browse files
committed
feat: Refactor JSObject finalization and enhance NSTimerHandle management in macOS
1 parent 4ec3407 commit ccfe268

4 files changed

Lines changed: 176 additions & 48 deletions

File tree

NativeScript/ffi/JSObject.mm

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@
44

55
#import <Foundation/Foundation.h>
66

7-
void JSObject_finalize(napi_env, void* data, void*) {
8-
id obj = (id)data;
9-
[obj release];
10-
}
11-
127
@interface JSObject : NSObject {
138
napi_env env;
149
napi_ref ref;
1510
nativescript::ObjCBridgeState* bridgeState;
1611
}
1712

1813
- (instancetype)initWithEnv:(napi_env)env value:(napi_value)value;
14+
- (void)removeFromBridgeState;
1915
- (napi_value)value;
2016

2117
@end
2218

19+
void JSObject_finalize(napi_env, void* data, void*) {
20+
JSObject* obj = (JSObject*)data;
21+
[obj removeFromBridgeState];
22+
[obj release];
23+
}
24+
2325
@implementation JSObject
2426

2527
- (instancetype)initWithEnv:(napi_env)_env value:(napi_value)value {
@@ -30,10 +32,21 @@ - (instancetype)initWithEnv:(napi_env)_env value:(napi_value)value {
3032
napi_reference_ref(env, ref, &result);
3133
napi_wrap(env, value, self, nullptr, nullptr, nullptr);
3234
bridgeState = nativescript::ObjCBridgeState::InstanceData(env);
33-
bridgeState->objectRefs[self] = ref;
35+
if (bridgeState != nullptr) {
36+
bridgeState->objectRefs[self] = ref;
37+
}
3438
return self;
3539
}
3640

41+
- (void)removeFromBridgeState {
42+
if (bridgeState == nullptr) {
43+
return;
44+
}
45+
46+
bridgeState->objectRefs.erase(self);
47+
bridgeState = nullptr;
48+
}
49+
3750
- (napi_value)value {
3851
napi_value result;
3952
napi_get_reference_value(env, ref, &result);
@@ -42,7 +55,6 @@ - (napi_value)value {
4255

4356
- (void)dealloc {
4457
napi_delete_reference(env, ref);
45-
bridgeState->objectRefs.erase(self);
4658
[super dealloc];
4759
}
4860

NativeScript/runtime/modules/timers/Timers.mm

Lines changed: 144 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,72 @@
44
#ifdef __APPLE__
55

66
#import <Foundation/Foundation.h>
7+
#include <dispatch/dispatch.h>
78
#include <objc/runtime.h>
9+
#include <cmath>
810
#include "Timers.h"
911

12+
@interface NSTimerHandle : NSObject {
13+
@public
14+
NSTimer* timer;
15+
napi_env env;
16+
napi_ref callback;
17+
}
18+
@end
19+
20+
@implementation NSTimerHandle
21+
- (void)dealloc {
22+
if (timer != nil) {
23+
[timer release];
24+
timer = nil;
25+
}
26+
[super dealloc];
27+
}
28+
@end
29+
30+
namespace {
31+
const void* kTimerHandleAssociationKey = &kTimerHandleAssociationKey;
32+
33+
void AddTimerToMainRunLoop(NSTimer* timer) {
34+
if (timer == nil) {
35+
return;
36+
}
37+
38+
auto addTimer = ^{
39+
if ([timer isValid]) {
40+
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
41+
}
42+
};
43+
44+
if ([NSThread isMainThread]) {
45+
addTimer();
46+
return;
47+
}
48+
49+
dispatch_sync(dispatch_get_main_queue(), addTimer);
50+
}
51+
52+
void DisposeTimerHandle(napi_env callEnv, NSTimerHandle* handle) {
53+
if (handle == nil) {
54+
return;
55+
}
56+
57+
if (handle->timer != nil) {
58+
NSTimer* rawTimer = handle->timer;
59+
objc_setAssociatedObject(rawTimer, kTimerHandleAssociationKey, nil, OBJC_ASSOCIATION_ASSIGN);
60+
[rawTimer invalidate];
61+
[rawTimer release];
62+
handle->timer = nil;
63+
}
64+
65+
napi_env cleanupEnv = callEnv != nullptr ? callEnv : handle->env;
66+
if (cleanupEnv != nullptr && handle->callback != nullptr) {
67+
napi_delete_reference(cleanupEnv, handle->callback);
68+
handle->callback = nullptr;
69+
}
70+
}
71+
} // namespace
72+
1073
namespace nativescript {
1174

1275
JS_CLASS_INIT(Timers::Init) {
@@ -29,33 +92,65 @@
2992
napi_value argv[2];
3093
napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
3194

32-
double ms;
33-
napi_get_value_double(env, argv[1], &ms);
95+
double ms = 0;
96+
if (argc > 1) {
97+
napi_get_value_double(env, argv[1], &ms);
98+
}
99+
if (ms < 0 || !std::isfinite(ms)) {
100+
ms = 0;
101+
}
34102

35103
NSTimeInterval interval = ms / 1000;
36104

37105
napi_ref callback;
38106
napi_create_reference(env, argv[0], 1, &callback);
39107

108+
NSTimerHandle* handle = [[NSTimerHandle alloc] init];
109+
handle->env = env;
110+
handle->callback = callback;
111+
40112
NSTimer* timer = [NSTimer
41113
timerWithTimeInterval:interval
42114
repeats:NO
43115
block:^(NSTimer* timer) {
44-
NapiScope scope(env);
116+
NSTimerHandle* handle = (NSTimerHandle*)objc_getAssociatedObject(
117+
timer, kTimerHandleAssociationKey);
118+
if (handle == nil || handle->callback == nullptr) {
119+
return;
120+
}
121+
[handle retain];
122+
123+
NapiScope scope(handle->env);
45124
napi_value global, callbackValue;
46-
napi_get_global(env, &global);
47-
napi_get_reference_value(env, callback, &callbackValue);
48-
napi_call_function(env, global, callbackValue, 0, nullptr, nullptr);
49-
napi_reference_unref(env, callback, nullptr);
50-
objc_setAssociatedObject(timer, "callback", nil, OBJC_ASSOCIATION_ASSIGN);
125+
napi_get_global(handle->env, &global);
126+
napi_get_reference_value(handle->env, handle->callback, &callbackValue);
127+
napi_call_function(handle->env, global, callbackValue, 0, nullptr, nullptr);
128+
129+
napi_delete_reference(handle->env, handle->callback);
130+
handle->callback = nullptr;
131+
objc_setAssociatedObject(timer, kTimerHandleAssociationKey, nil,
132+
OBJC_ASSOCIATION_ASSIGN);
133+
if (handle->timer != nil) {
134+
[handle->timer release];
135+
handle->timer = nil;
136+
}
137+
[handle release];
51138
}];
52139

53-
objc_setAssociatedObject(timer, "callback", (id)callback, OBJC_ASSOCIATION_ASSIGN);
140+
handle->timer = [timer retain];
141+
objc_setAssociatedObject(timer, kTimerHandleAssociationKey, handle,
142+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
54143

55144
napi_value result;
56-
napi_create_external(env, timer, nullptr, nullptr, &result);
145+
napi_create_external(
146+
env, handle,
147+
[](napi_env env, void* data, void* /*hint*/) {
148+
NSTimerHandle* handle = (NSTimerHandle*)data;
149+
[handle release];
150+
},
151+
nullptr, &result);
57152

58-
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
153+
AddTimerToMainRunLoop(timer);
59154

60155
return result;
61156
}
@@ -65,31 +160,56 @@
65160
napi_value argv[2];
66161
napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
67162

68-
double ms;
69-
napi_get_value_double(env, argv[1], &ms);
163+
double ms = 0;
164+
if (argc > 1) {
165+
napi_get_value_double(env, argv[1], &ms);
166+
}
167+
if (ms < 0 || !std::isfinite(ms)) {
168+
ms = 0;
169+
}
70170

71171
NSTimeInterval interval = ms / 1000;
72172

73173
napi_ref callback;
74174
napi_create_reference(env, argv[0], 1, &callback);
75175

176+
NSTimerHandle* handle = [[NSTimerHandle alloc] init];
177+
handle->env = env;
178+
handle->callback = callback;
179+
76180
NSTimer* timer = [NSTimer
77181
timerWithTimeInterval:interval
78182
repeats:YES
79183
block:^(NSTimer* timer) {
80-
NapiScope scope(env);
184+
NSTimerHandle* handle = (NSTimerHandle*)objc_getAssociatedObject(
185+
timer, kTimerHandleAssociationKey);
186+
if (handle == nil || handle->callback == nullptr) {
187+
return;
188+
}
189+
[handle retain];
190+
191+
NapiScope scope(handle->env);
81192
napi_value global, callbackValue;
82-
napi_get_global(env, &global);
83-
napi_get_reference_value(env, callback, &callbackValue);
84-
napi_call_function(env, global, callbackValue, 0, nullptr, nullptr);
193+
napi_get_global(handle->env, &global);
194+
napi_get_reference_value(handle->env, handle->callback, &callbackValue);
195+
napi_call_function(handle->env, global, callbackValue, 0, nullptr, nullptr);
196+
[handle release];
85197
}];
86198

87-
objc_setAssociatedObject(timer, "callback", (id)callback, OBJC_ASSOCIATION_ASSIGN);
199+
handle->timer = [timer retain];
200+
objc_setAssociatedObject(timer, kTimerHandleAssociationKey, handle,
201+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
88202

89203
napi_value result;
90-
napi_create_external(env, timer, nullptr, nullptr, &result);
204+
napi_create_external(
205+
env, handle,
206+
[](napi_env env, void* data, void* /*hint*/) {
207+
NSTimerHandle* handle = (NSTimerHandle*)data;
208+
[handle release];
209+
},
210+
nullptr, &result);
91211

92-
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
212+
AddTimerToMainRunLoop(timer);
93213

94214
return result;
95215
}
@@ -105,17 +225,10 @@
105225
return nullptr;
106226
}
107227

108-
NSTimer* t = nullptr;
109-
napi_get_value_external(env, argv[0], (void**)&t);
110-
111-
if (t != nullptr) {
112-
[t invalidate];
113-
}
114-
115-
napi_ref callback = (napi_ref)objc_getAssociatedObject(t, "callback");
116-
if (callback != nil) {
117-
napi_delete_reference(env, callback);
118-
}
228+
void* rawHandle = nullptr;
229+
napi_get_value_external(env, argv[0], &rawHandle);
230+
NSTimerHandle* handle = (NSTimerHandle*)rawHandle;
231+
DisposeTimerHandle(env, handle);
119232

120233
return nullptr;
121234
}

TestRunner/app/tests/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ loadTest("./Modules");
214214
//
215215
loadTest("./RuntimeImplementedAPIs");
216216

217-
// require("./Timers");
217+
require("./Timers");
218218

219219
require("./URL");
220220
loadTest("./URLSearchParams");

metadata-generator/src/Umbrella.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,24 +148,27 @@ static std::error_code CreateUmbrellaHeaderForAmbientModulesInner(
148148
return std::error_code();
149149
}
150150

151-
// On macOS SDKs, keep only objc runtime headers from usr/include so we keep
152-
// symbols like class_getName while avoiding broad private header parsing.
151+
// On macOS SDKs, keep only selected public runtime headers from usr/include
152+
// so we avoid broad private header parsing while preserving compatibility.
153153
if (dir.find("/MacOSX.platform/Developer/SDKs/") != std::string::npos &&
154154
dir.ends_with("/usr/include")) {
155-
std::filesystem::path objcHeadersPath = std::filesystem::path(dir) / "objc";
156-
if (std::filesystem::exists(objcHeadersPath)) {
157-
if (std::error_code code = CreateUmbrellaHeaderForAmbientModulesInner(
158-
objcHeadersPath.string(), false, umbrellaHeaders, includePaths,
159-
frameworks, umbrellaHeaderSet, includePathSet, frameworkSet)) {
160-
return code;
155+
for (const char* subdir : {"objc", "dispatch"}) {
156+
std::filesystem::path headersPath = std::filesystem::path(dir) / subdir;
157+
if (std::filesystem::exists(headersPath)) {
158+
if (std::error_code code = CreateUmbrellaHeaderForAmbientModulesInner(
159+
headersPath.string(), false, umbrellaHeaders, includePaths,
160+
frameworks, umbrellaHeaderSet, includePathSet, frameworkSet)) {
161+
return code;
162+
}
161163
}
162164
}
163165
return std::error_code();
164166
}
165167

166168
if (dir.find("/MacOSX.platform/Developer/SDKs/") != std::string::npos &&
167169
dir.find("/usr/include/") != std::string::npos &&
168-
dir.find("/usr/include/objc") == std::string::npos) {
170+
dir.find("/usr/include/objc") == std::string::npos &&
171+
dir.find("/usr/include/dispatch") == std::string::npos) {
169172
return std::error_code();
170173
}
171174

0 commit comments

Comments
 (0)