Skip to content

Commit cb91a92

Browse files
committed
feat: Enhance lifecycle management and improve type handling in ObjC bridge
1 parent e91fa8a commit cb91a92

8 files changed

Lines changed: 280 additions & 87 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,7 @@ v8_build
5959

6060
.cache/
6161

62+
dist
63+
packages/*/types
64+
6265
SwiftBindgen

NativeScript/ffi/ObjCBridge.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
569569
}
570570

571571
objectRefs[nativeObject] = ref;
572+
attachObjectLifecycleAssociation(env, nativeObject);
572573

573574
return result;
574575
}

NativeScript/ffi/Object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace nativescript {
77

88
void initProxyFactory(napi_env env, ObjCBridgeState* bridgeState);
9+
void attachObjectLifecycleAssociation(napi_env env, id object);
910

1011
} // namespace nativescript
1112

NativeScript/ffi/Object.mm

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include "node_api_util.h"
77

88
#import <Foundation/Foundation.h>
9+
#import <dispatch/dispatch.h>
910
#include <objc/runtime.h>
1011

1112
static SEL JSWrapperObjectAssociationKey = @selector(JSWrapperObjectAssociationKey);
13+
static SEL ObjCLifecycleAssociationKey = @selector(ObjCLifecycleAssociationKey);
1214

1315
@interface JSWrapperObjectAssociation : NSObject
1416

@@ -23,6 +25,15 @@ - (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref;
2325

2426
@end
2527

28+
@interface ObjCLifecycleAssociation : NSObject {
29+
napi_env _env;
30+
uintptr_t _objectAddress;
31+
}
32+
33+
- (instancetype)initWithEnv:(napi_env)env object:(id)object;
34+
35+
@end
36+
2637
@implementation JSWrapperObjectAssociation
2738

2839
- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref {
@@ -53,6 +64,33 @@ - (void)dealloc {
5364

5465
@end
5566

67+
@implementation ObjCLifecycleAssociation
68+
69+
- (instancetype)initWithEnv:(napi_env)env object:(id)object {
70+
self = [super init];
71+
if (self) {
72+
_env = env;
73+
_objectAddress = (uintptr_t)object;
74+
}
75+
76+
return self;
77+
}
78+
79+
- (void)dealloc {
80+
napi_env env = _env;
81+
uintptr_t objectAddress = _objectAddress;
82+
dispatch_async(dispatch_get_main_queue(), ^{
83+
nativescript::ObjCBridgeState* bridgeState = nativescript::ObjCBridgeState::InstanceData(env);
84+
if (bridgeState != nullptr) {
85+
bridgeState->objectRefs.erase((id)objectAddress);
86+
}
87+
});
88+
89+
[super dealloc];
90+
}
91+
92+
@end
93+
5694
napi_value JS_transferOwnershipToNative(napi_env env, napi_callback_info cbinfo) {
5795
size_t argc = 1;
5896
napi_value arg;
@@ -120,6 +158,22 @@ void initProxyFactory(napi_env env, ObjCBridgeState* state) {
120158
state->transferOwnershipToNative = make_ref(env, transferOwnershipToNative);
121159
}
122160

161+
void attachObjectLifecycleAssociation(napi_env env, id object) {
162+
if (object == nil) {
163+
return;
164+
}
165+
166+
if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) {
167+
return;
168+
}
169+
170+
ObjCLifecycleAssociation* association = [[ObjCLifecycleAssociation alloc] initWithEnv:env
171+
object:object];
172+
objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association,
173+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
174+
[association release];
175+
}
176+
123177
void finalize_objc_object(napi_env /*env*/, void* data, void* hint) {
124178
id object = static_cast<id>(data);
125179
ObjCBridgeState* bridgeState = static_cast<ObjCBridgeState*>(hint);

NativeScript/ffi/TypeConv.mm

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,21 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
173173
std::string structname;
174174
const char* nameStart = *encoding + 1; // skip '{'
175175
const char* c = nameStart;
176-
while (*c != '=') {
176+
while (*c != '\0' && *c != '=') {
177177
structname += *c;
178178
c++;
179179
}
180+
if (*c != '=') {
181+
// Malformed struct encoding. Advance to the end of this token and
182+
// fallback to pointer conversion to avoid reading past the buffer.
183+
while (**encoding != '\0' && **encoding != '}') {
184+
(*encoding)++;
185+
}
186+
if (**encoding == '}') {
187+
(*encoding)++;
188+
}
189+
return &ffi_type_pointer;
190+
}
180191

181192
// Check if we're already processing this struct (cycle detection)
182193
if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) {
@@ -201,16 +212,18 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
201212
}
202213

203214
// Check if we already have a forward declaration for this struct
204-
auto forwardIt = forwardDeclaredEncodingStructs.find(structname);
205-
if (forwardIt != forwardDeclaredEncodingStructs.end()) {
215+
auto existingForwardIt = forwardDeclaredEncodingStructs.find(structname);
216+
if (existingForwardIt != forwardDeclaredEncodingStructs.end()) {
206217
// Skip the struct encoding
207218
(*encoding)++; // skip '{'
208-
while (**encoding != '}') {
219+
while (**encoding != '\0' && **encoding != '}') {
209220
(*encoding)++;
210221
}
211-
(*encoding)++; // skip '}'
222+
if (**encoding == '}') {
223+
(*encoding)++; // skip '}'
224+
}
212225

213-
return forwardIt->second;
226+
return existingForwardIt->second;
214227
}
215228

216229
// Mark this struct as being processed
@@ -226,18 +239,25 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
226239

227240
(*encoding)++; // skip '{'
228241

229-
while (**encoding != '=') {
242+
while (**encoding != '\0' && **encoding != '=') {
230243
(*encoding)++;
231244
} // skip name
245+
if (**encoding == '\0') {
246+
processingEncodingStructs.erase(structname);
247+
delete type;
248+
return &ffi_type_pointer;
249+
}
232250

233251
(*encoding)++; // skip '='
234252

235-
while (**encoding != '}') {
253+
while (**encoding != '\0' && **encoding != '}') {
236254
ffi_type* elementType = TypeConv::Make(env, encoding)->type;
237255
elements.push_back(elementType);
238256
}
239257

240-
(*encoding)++; // skip '}'
258+
if (**encoding == '}') {
259+
(*encoding)++; // skip '}'
260+
}
241261

242262
type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1));
243263
for (int i = 0; i < elements.size(); i++) {
@@ -247,8 +267,9 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
247267
type->elements[elements.size()] = nullptr;
248268

249269
// If this was a forward declaration, update it with the real layout
250-
if (forwardIt != forwardDeclaredEncodingStructs.end()) {
251-
ffi_type* forwardType = forwardIt->second;
270+
auto resolvedForwardIt = forwardDeclaredEncodingStructs.find(structname);
271+
if (resolvedForwardIt != forwardDeclaredEncodingStructs.end()) {
272+
ffi_type* forwardType = resolvedForwardIt->second;
252273
forwardType->type = type->type;
253274
forwardType->size = type->size;
254275
forwardType->alignment = type->alignment;
@@ -257,7 +278,7 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
257278
// Clean up the temporary type and use the forward declaration
258279
delete type;
259280
type = forwardType;
260-
forwardDeclaredEncodingStructs.erase(forwardIt);
281+
forwardDeclaredEncodingStructs.erase(resolvedForwardIt);
261282
}
262283

263284
// Remove from processing set
@@ -283,9 +304,9 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
283304
}
284305

285306
// Check if we already have a forward declaration for this struct
286-
auto forwardIt = forwardDeclaredStructs.find(structOffset);
287-
if (forwardIt != forwardDeclaredStructs.end()) {
288-
return forwardIt->second;
307+
auto existingForwardIt = forwardDeclaredStructs.find(structOffset);
308+
if (existingForwardIt != forwardDeclaredStructs.end()) {
309+
return existingForwardIt->second;
289310
}
290311

291312
// Mark this struct as being processed
@@ -326,8 +347,9 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
326347
type->elements[elements.size()] = nullptr;
327348

328349
// If this was a forward declaration, update it with the real layout
329-
if (forwardIt != forwardDeclaredStructs.end()) {
330-
ffi_type* forwardType = forwardIt->second;
350+
auto resolvedForwardIt = forwardDeclaredStructs.find(structOffset);
351+
if (resolvedForwardIt != forwardDeclaredStructs.end()) {
352+
ffi_type* forwardType = resolvedForwardIt->second;
331353
forwardType->type = type->type;
332354
forwardType->size = type->size;
333355
forwardType->alignment = type->alignment;
@@ -336,7 +358,7 @@ MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const cha
336358
// Clean up the temporary type and use the forward declaration
337359
delete type;
338360
type = forwardType;
339-
forwardDeclaredStructs.erase(forwardIt);
361+
forwardDeclaredStructs.erase(resolvedForwardIt);
340362
}
341363

342364
// Remove from processing set
@@ -3101,10 +3123,19 @@ void writeFromArrayElements(napi_env env, napi_value value, void* result, bool*
31013123
case '{': {
31023124
std::string structname;
31033125
const char* c = *encoding + 1;
3104-
while (*c != '=') {
3126+
while (*c != '\0' && *c != '=') {
31053127
structname += *c;
31063128
c++;
31073129
}
3130+
if (*c != '=') {
3131+
while (**encoding != '\0' && **encoding != '}') {
3132+
(*encoding)++;
3133+
}
3134+
if (**encoding == '}') {
3135+
(*encoding)++;
3136+
}
3137+
return pointerTypeConv;
3138+
}
31083139

31093140
// Check if we already have a cached StructTypeConv for this encoding-based struct
31103141
auto cacheIt = encodingStructCache.find(structname);
@@ -3113,9 +3144,13 @@ void writeFromArrayElements(napi_env env, napi_value value, void* result, bool*
31133144
}
31143145

31153146
auto bridgeState = ObjCBridgeState::InstanceData(env);
3116-
// NSLog(@"struct: %s, %d", structname.c_str(),
3117-
// bridgeState->structOffsets[structname]);
3118-
auto structOffset = bridgeState->structOffsets[structname];
3147+
MDSectionOffset structOffset = MD_SECTION_OFFSET_NULL;
3148+
if (bridgeState != nullptr) {
3149+
auto structOffsetIt = bridgeState->structOffsets.find(structname);
3150+
if (structOffsetIt != bridgeState->structOffsets.end()) {
3151+
structOffset = structOffsetIt->second;
3152+
}
3153+
}
31193154
auto type = typeFromStruct(env, encoding);
31203155
auto structTypeConv = std::make_shared<StructTypeConv>(StructTypeConv(structOffset, type));
31213156

NativeScript/napi/common/native_api_util.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct char_traits<unsigned short> {
7979

8080
#define NAPI_CALLBACK_BEGIN_VARGS() \
8181
napi_status status; \
82-
size_t argc; \
82+
size_t argc = 0; \
8383
void* data; \
8484
napi_value jsThis; \
8585
NAPI_GUARD(napi_get_cb_info(env, info, &argc, nullptr, &jsThis, &data)) { \
@@ -609,4 +609,4 @@ inline void napi_inherits(napi_env env, napi_value ctor,
609609

610610
} // namespace napi_util
611611

612-
#endif /* NATIVE_API_UTIL_H_ */
612+
#endif /* NATIVE_API_UTIL_H_ */

0 commit comments

Comments
 (0)