Skip to content

Commit 9388c48

Browse files
committed
feat: Enhance ObjC bridge state management with mutex and token handling
1 parent dfb9fcf commit 9388c48

File tree

4 files changed

+116
-13
lines changed

4 files changed

+116
-13
lines changed

NativeScript/ffi/ClassBuilder.mm

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,53 @@
99
#include "Util.h"
1010
#include "js_native_api.h"
1111
#include "node_api_util.h"
12+
#include <mutex>
1213

1314
namespace nativescript {
1415
namespace {
1516
std::unordered_map<std::string, MethodDescriptor> gKnownExposedMethods;
16-
napi_env gClassBuilderEnv = nullptr;
17+
std::mutex gClassBuilderEnvMutex;
18+
std::unordered_map<Class, napi_env> gClassBuilderEnvs;
19+
20+
void registerClassBuilderEnv(Class nativeClass, napi_env env) {
21+
if (nativeClass == Nil || env == nullptr) {
22+
return;
23+
}
24+
25+
std::lock_guard<std::mutex> lock(gClassBuilderEnvMutex);
26+
gClassBuilderEnvs[nativeClass] = env;
27+
}
28+
29+
void unregisterClassBuilderEnv(Class nativeClass) {
30+
if (nativeClass == Nil) {
31+
return;
32+
}
33+
34+
std::lock_guard<std::mutex> lock(gClassBuilderEnvMutex);
35+
gClassBuilderEnvs.erase(nativeClass);
36+
}
37+
38+
napi_env resolveClassBuilderEnv(id self) {
39+
if (self == nil) {
40+
return nullptr;
41+
}
42+
43+
Class currentClass = object_getClass(self);
44+
if (currentClass == Nil) {
45+
return nullptr;
46+
}
47+
48+
std::lock_guard<std::mutex> lock(gClassBuilderEnvMutex);
49+
while (currentClass != Nil) {
50+
auto find = gClassBuilderEnvs.find(currentClass);
51+
if (find != gClassBuilderEnvs.end()) {
52+
return find->second;
53+
}
54+
currentClass = class_getSuperclass(currentClass);
55+
}
56+
57+
return nullptr;
58+
}
1759

1860
const char* kInstallSuperAccessorSource = R"(
1961
(function (prototype, basePrototype) {
@@ -201,11 +243,15 @@ napi_value JS_classConformsToProtocolSafe(napi_env env, napi_callback_info info)
201243

202244
NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerationState* state,
203245
id __unsafe_unretained stackbuf[], NSUInteger len) {
204-
if (len == 0 || gClassBuilderEnv == nullptr) {
246+
if (len == 0) {
247+
return 0;
248+
}
249+
250+
napi_env env = resolveClassBuilderEnv(self);
251+
if (env == nullptr) {
205252
return 0;
206253
}
207254

208-
napi_env env = gClassBuilderEnv;
209255
ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env);
210256
if (bridgeState == nullptr) {
211257
return 0;
@@ -343,7 +389,6 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
343389

344390
ClassBuilder::ClassBuilder(napi_env env, napi_value constructor) {
345391
this->env = env;
346-
gClassBuilderEnv = env;
347392
bridgeState = ObjCBridgeState::InstanceData(env);
348393

349394
metadataOffset = MD_SECTION_OFFSET_NULL;
@@ -399,6 +444,7 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
399444
class_addProtocol(nativeClass, @protocol(ObjCBridgeClassBuilderProtocol));
400445

401446
objc_registerClassPair(nativeClass);
447+
registerClassBuilderEnv(nativeClass, env);
402448

403449
napi_remove_wrap(env, constructor, nullptr);
404450

@@ -412,6 +458,8 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
412458
}
413459

414460
ClassBuilder::~ClassBuilder() {
461+
unregisterClassBuilderEnv(nativeClass);
462+
415463
if (nativeClass != nullptr) {
416464
objc_disposeClassPair(nativeClass);
417465
napi_delete_reference(env, constructor);

NativeScript/ffi/ObjCBridge.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ using namespace metagen;
2929

3030
namespace nativescript {
3131

32+
class ObjCBridgeState;
33+
3234
void finalize_objc_object(napi_env /*env*/, void* data, void* hint);
35+
bool IsBridgeStateLive(const ObjCBridgeState* bridgeState,
36+
uint64_t token) noexcept;
3337

3438
// Determines how retain/release should be called when an Objective-C
3539
// object is exposed to JavaScript land.
@@ -193,6 +197,7 @@ class ObjCBridgeState {
193197
}
194198

195199
public:
200+
uint64_t lifetimeToken = 0;
196201
std::unordered_map<id, napi_ref> objectRefs;
197202

198203
napi_ref pointerClass = nullptr;

NativeScript/ffi/ObjCBridge.mm

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
#include <mach-o/dyld.h>
2525
#include <mach-o/getsect.h>
2626
#include <objc/runtime.h>
27+
#include <atomic>
2728
#include <cstring>
2829
#include <initializer_list>
30+
#include <mutex>
2931

3032
#ifdef EMBED_METADATA_SIZE
3133
const unsigned char __attribute__((section("__objc_metadata,__objc_metadata")))
@@ -37,6 +39,42 @@ const unsigned char __attribute__((section("__objc_metadata,__objc_metadata")))
3739
#endif
3840

3941
namespace nativescript {
42+
namespace {
43+
std::mutex gLiveBridgeStatesMutex;
44+
std::unordered_map<const ObjCBridgeState*, uint64_t> gLiveBridgeStates;
45+
std::atomic<uint64_t> gNextBridgeStateToken{1};
46+
47+
uint64_t RegisterBridgeState(const ObjCBridgeState* bridgeState) {
48+
if (bridgeState == nullptr) {
49+
return 0;
50+
}
51+
52+
uint64_t token = gNextBridgeStateToken.fetch_add(1, std::memory_order_relaxed);
53+
std::lock_guard<std::mutex> lock(gLiveBridgeStatesMutex);
54+
gLiveBridgeStates[bridgeState] = token;
55+
return token;
56+
}
57+
58+
void UnregisterBridgeState(const ObjCBridgeState* bridgeState) {
59+
if (bridgeState == nullptr) {
60+
return;
61+
}
62+
63+
std::lock_guard<std::mutex> lock(gLiveBridgeStatesMutex);
64+
gLiveBridgeStates.erase(bridgeState);
65+
}
66+
} // namespace
67+
68+
bool IsBridgeStateLive(const ObjCBridgeState* bridgeState,
69+
uint64_t token) noexcept {
70+
if (bridgeState == nullptr || token == 0) {
71+
return false;
72+
}
73+
74+
std::lock_guard<std::mutex> lock(gLiveBridgeStatesMutex);
75+
auto find = gLiveBridgeStates.find(bridgeState);
76+
return find != gLiveBridgeStates.end() && find->second == token;
77+
}
4078

4179
void finalize_bridge_data(napi_env env, void* data, void* hint) {
4280
auto bridgeState = (ObjCBridgeState*)data;
@@ -459,6 +497,7 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
459497
ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path,
460498
const void* metadata_ptr) {
461499
napi_set_instance_data(env, this, finalize_bridge_data, nil);
500+
lifetimeToken = RegisterBridgeState(this);
462501

463502
self_dl = dlopen(nullptr, RTLD_NOW);
464503

@@ -495,6 +534,8 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
495534
}
496535

497536
ObjCBridgeState::~ObjCBridgeState() {
537+
UnregisterBridgeState(this);
538+
498539
// Clean up cached Cif objects
499540
for (auto& pair : cifs) {
500541
delete pair.second;

NativeScript/ffi/Object.mm

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ - (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref;
2626
@end
2727

2828
@interface ObjCLifecycleAssociation : NSObject {
29-
napi_env _env;
29+
nativescript::ObjCBridgeState* _bridgeState;
30+
uint64_t _bridgeStateToken;
3031
uintptr_t _objectAddress;
3132
}
3233

33-
- (instancetype)initWithEnv:(napi_env)env object:(id)object;
34+
- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState
35+
object:(id)object;
3436

3537
@end
3638

@@ -66,22 +68,24 @@ - (void)dealloc {
6668

6769
@implementation ObjCLifecycleAssociation
6870

69-
- (instancetype)initWithEnv:(napi_env)env object:(id)object {
71+
- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState
72+
object:(id)object {
7073
self = [super init];
7174
if (self) {
72-
_env = env;
75+
_bridgeState = bridgeState;
76+
_bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0;
7377
_objectAddress = (uintptr_t)object;
7478
}
7579

7680
return self;
7781
}
7882

7983
- (void)dealloc {
80-
napi_env env = _env;
84+
nativescript::ObjCBridgeState* bridgeState = _bridgeState;
85+
uint64_t bridgeStateToken = _bridgeStateToken;
8186
uintptr_t objectAddress = _objectAddress;
8287
dispatch_async(dispatch_get_main_queue(), ^{
83-
nativescript::ObjCBridgeState* bridgeState = nativescript::ObjCBridgeState::InstanceData(env);
84-
if (bridgeState != nullptr) {
88+
if (nativescript::IsBridgeStateLive(bridgeState, bridgeStateToken)) {
8589
bridgeState->objectRefs.erase((id)objectAddress);
8690
}
8791
});
@@ -163,12 +167,17 @@ void attachObjectLifecycleAssociation(napi_env env, id object) {
163167
return;
164168
}
165169

170+
auto bridgeState = ObjCBridgeState::InstanceData(env);
171+
if (bridgeState == nullptr) {
172+
return;
173+
}
174+
166175
if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) {
167176
return;
168177
}
169178

170-
ObjCLifecycleAssociation* association = [[ObjCLifecycleAssociation alloc] initWithEnv:env
171-
object:object];
179+
ObjCLifecycleAssociation* association =
180+
[[ObjCLifecycleAssociation alloc] initWithBridgeState:bridgeState object:object];
172181
objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association,
173182
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
174183
[association release];

0 commit comments

Comments
 (0)