From c210aa5c812bb13ce19de6b454e3c1fae75e95c1 Mon Sep 17 00:00:00 2001 From: Adrian Niculescu Date: Sun, 14 Jun 2026 00:14:31 +0300 Subject: [PATCH 1/2] Allow indexed access for non-NSArray collections Indexed access from JS (obj[i]) only worked when the native object was an NSArray, because the getter checked isKindOfClass:[NSArray class]. Switched that to a capability check so anything responding to both count and objectAtIndex: is indexable too, like PHFetchResult and NSOrderedSet. The setter still only accepts NSMutableArray so we don't try to mutate read-only collections. --- NativeScript/runtime/ArgConverter.mm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index bd6956c6..493b3af5 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -883,16 +883,18 @@ ObjCDataWrapper* objcDataWrapper = static_cast(wrapper); id target = objcDataWrapper->Data(); - if (![target isKindOfClass:[NSArray class]]) { + // Treat anything that can report a count and return elements by index as + // indexable, not just NSArray (e.g. PHFetchResult, NSOrderedSet). + if (![target respondsToSelector:@selector(count)] || + ![target respondsToSelector:@selector(objectAtIndex:)]) { return; } - NSArray* array = (NSArray*)target; - if (index >= [array count]) { + if (index >= [target count]) { return; } - id obj = [array objectAtIndex:index]; + id obj = [target objectAtIndex:index]; std::shared_ptr cache = Caches::Get(isolate); auto it = cache->Instances.find(obj); From 0bd3978c823ef216dbf52b2e8ea18e92b0f55413 Mon Sep 17 00:00:00 2001 From: Adrian Niculescu Date: Sun, 14 Jun 2026 00:25:56 +0300 Subject: [PATCH 2/2] Added regression test for indexed access on non-NSArray collections --- TestRunner/app/tests/ApiTests.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/TestRunner/app/tests/ApiTests.js b/TestRunner/app/tests/ApiTests.js index 84834d17..99b032f0 100644 --- a/TestRunner/app/tests/ApiTests.js +++ b/TestRunner/app/tests/ApiTests.js @@ -22,6 +22,20 @@ describe(module.id, function () { expect(res[0].constructor.name).toEqual("NSURL"); }); + it("indexed access works for non-NSArray indexable collections", function () { + // NSOrderedSet is not an NSArray but responds to count + objectAtIndex:, + // so obj[i] should resolve the same elements as objectAtIndex(i). + const set = NSOrderedSet.orderedSetWithArray(['a', 'b', 'c']); + expect(set.count).toBe(3); + expect(set[0]).toBe(set.objectAtIndex(0)); + expect(set[1]).toBe(set.objectAtIndex(1)); + expect(set[2]).toBe(set.objectAtIndex(2)); + expect(set[0]).toBe('a'); + expect(set[2]).toBe('c'); + // Out-of-range index returns undefined instead of throwing. + expect(set[3]).toBeUndefined(); + }); + it("MethodCalledInDealloc", function () { expect(function () { (function () {