GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(swift-interop): adapter lifetime, cancellation, subscripts, Combine and operators (6/7) by NathanWalker · Pull Request #523 · NativeScript/ios

Conversation

@NathanWalker

@NathanWalker NathanWalker commented Oct 21, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Summary

Swift interop stack — review bottom-up; each PR shows only its own commits. Merge in order; after a squash merge, rebase the next branch onto main. Design discussion: RFC NativeScript/rfcs#57.

  1. feat(metadata-generator): Swift symbol graphs to thunks and TypeScript declarations (Swift interop 1/7) #518 generator: symbol graphs → IR → thunks + .d.ts
  2. feat(runtime): ns:swift calls Swift through generated thunks (Swift interop 2/7) #519 runtime: ns:swift core
  3. feat(swift-interop): modules, generics, protocols, Observation and SwiftUI (3/7) #520 modules, generics, protocols, Observation, SwiftUI
  4. feat(swift-interop): generate and link thunks in app builds (4/7) #521 app builds
  5. feat(swift-interop): returning closures, generic functions, tuples, Result and C structs (5/7) #522 more values: returning closures, generic functions, tuples, Result, C structs
  6. feat(swift-interop): adapter lifetime, cancellation, subscripts, Combine and operators (6/7) #523 adapter lifetime, cancellation, subscripts, Combine, operators ← this PR
  7. feat(swift-interop): Any, UUID and Decimal, performance, and docs (Swift interop 7/7) #517 Any/UUID/Decimal, performance, docs

Lifetime and call-shape work.

SwiftDemo.introduce(delegate, "ada");            // one adapter per JS object; weak Swift delegates live as long as JS holds them
await SwiftDemo.waitForCancel(10, controller.signal); // AbortSignal rejects and cancels the Swift task
shelf.setSubscript("apples", 3); shelf.subscript("apples"); // subscripts
for await (const v of publisher.values) { break; } // AsyncSequence types, Combine publishers, early exit ends the stream
a.plus(b); versions.sort((x, y) => x.compare(y)); // operators as named methods, Equatable/Comparable
SwiftDemo.sumAll([1, 2, 3]);                     // some Sequence<Int>, C: Collection, any Sequence<E> take arrays

Protocol adapters follow NativeScript's lifetime rule for the JS side of Objective-C objects (ABI 14). Async calls take a trailing AbortSignal (ABI 15).

Evidence

  • Before: each call made a new adapter, so Swift saw a new identity each time (removeAll { $0 === listener } never matched), and a weakly held adapter was freed as soon as the call returned. Async calls could not be cancelled.
    After: 134 ns:swift TestRunner specs (17 new), covering adapter identity, weak delegates across GC, cancellation, subscripts, Combine and collection protocols.

Merge Danger

Door: two-way

Blast Radius: ns:swift

Adapters hold JS objects strongly while Swift shares them and weakly otherwise, so leak or early-release bugs would show here first.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
  • X
  • Mastodon
  • Reddit
  • LinkedIn

Comment @coderabbitai help to get the list of available commands.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Oct 21, 2026
feat(metadata-generator): Swift symbol graphs to thunks and TypeScript declarations (Swift interop 1/7) #518
Draft
feat(runtime): ns:swift calls Swift through generated thunks (Swift interop 2/7) #519
Draft
feat(swift-interop): modules, generics, protocols, Observation and SwiftUI (3/7) #520
Draft
feat(swift-interop): generate and link thunks in app builds (4/7) #521
Draft
feat(swift-interop): returning closures, generic functions, tuples, Result and C structs (5/7) #522
Draft
feat(swift-interop): Any, UUID and Decimal, performance, and docs (Swift interop 7/7) #517
Draft
@NathanWalker
NathanWalker marked this pull request as ready for review October 21, 2026 13:50
@NathanWalker
NathanWalker force-pushed the swift-interop/6-lifetime-async branch from 4eb9b59 to d43b891 Compare October 21, 2026 14:47
@NathanWalker
NathanWalker force-pushed the swift-interop/5-values branch from 8c4d970 to 7a3876e Compare October 21, 2026 14:47
@NathanWalker
NathanWalker marked this pull request as draft October 21, 2026 16:34
NathanWalker added 6 commits October 21, 2026 09:44
@NathanWalker
fix(swift-interop): one adapter per JS object, with NativeScript's li…
90b75ce
…fetime rule (ABI 14)

A JS object passed as a protocol value now gets one Swift adapter per
protocol instead of a new one per call. Swift sees one identity, so
`removeAll { $0 === listener }` matches.

The runtime owns each adapter while its JS object lives. Before each
garbage collection it holds the JS object strongly if anything else
retains the adapter, and weakly otherwise. This follows NativeScript's
rule for the JS side of Objective-C objects:
- strong Swift references (`var delegate`) keep the JS object alive;
- `weak var delegate` holds it for as long as JS keeps it, then becomes
  nil.
Once neither side holds them, both are collected. Previously a weakly
held adapter was freed as soon as the call returned.

Calls through an adapter whose JS object was collected fail like a JS
implementation that throws.

Thunk ABI 14: API slots 54 (adapterOf) and 55 (adoptAdapter).
@NathanWalker
feat(swift-interop): cancel async calls with an AbortSignal (ABI 15)
0f74c40
An async member now takes an optional AbortSignal after its arguments:
`await fetchItems(query, controller.signal)`. Aborting rejects the
Promise at once with the signal's reason, as fetch does, and cancels the
Swift task. Results the task reports afterwards are dropped. A signal
that is already aborted rejects without calling Swift. The signal is
taken off before overload resolution, so it never competes with real
arguments.

Async thunks keep their Task and register a cancel hook for the call's
token. TypeScript declares `signal?: AbortSignal` on async members, and
types/ns-swift.d.ts declares a minimal AbortSignal for programs without
the DOM library.

Thunk ABI 15: API slot 56 (setCancel).
@NathanWalker
feat(swift-interop): subscripts as subscript() and setSubscript() met…
8f0e4e7
…hods

A subscript with indices is now a JS method: `shelf.subscript("apples")`
reads it. A settable one also gets `shelf.setSubscript("apples", 3)`,
which assigns it, writing through a struct's box in place. Subscripts
are overloaded like methods, by their indices. Named methods keep
property access untouched and can't collide with a type's own `get` or
`set`.

The surface adds the setter as a synthetic member, and the emitter
spells `receiver[indices]` and `receiver[indices] = value`. Against the
iOS 18 SDK this adds 2 WeatherKit, 4 Foundation, 5 SwiftUI, 1 SwiftData
and 1 Charts callables. Every module's thunks still type-check.
@NathanWalker
feat(swift-interop): AsyncSequence types, Combine publishers, early l…
ccb1e64
…oop exit

A bridged class or struct conforming to AsyncSequence now keeps its JS
class and other members, and is also async-iterable through a synthetic
`[Symbol.asyncIterator]()`. The element comes from its `Element`
typealias, or else from its iterator's own `next()`.

AnyPublisher, PassthroughSubject and CurrentValueSubject are async
iterators over their `values` on iOS 15+, and null on earlier systems.
Thunks import Combine when one is bridged.

Async iterators now implement `return()`, so leaving a `for await` loop
early releases the Swift iterator. That ends a stream Swift still
produces: AsyncStream's onTermination sees `.cancelled`, and a
publisher's subscription is cancelled.
@NathanWalker
feat(swift-interop): operators as named methods, equals() and compare()
f5ecb44
Operators are now JS methods named for what they do:
- `+` `-` `*` `/` `%` are `plus`, `minus`, `times`, `dividedBy` and
  `remainder`;
- `==`, `<`, `<=`, `>` and `>=` are `equals`, `lessThan`,
  `lessThanOrEqualTo`, `greaterThan` and `greaterThanOrEqualTo`;
- prefix `-` and `!` are `negated()` and `not()`.

An operator whose left operand is its own type is an instance method of
that operand (`a.plus(b)`); any other is static (`Vector2.times(2, v)`).
Compound assignment, bitwise and custom operators stay unexposed.

Equatable classes and structs (also through Hashable or Comparable) now
get `equals(other)`, and Comparable ones get `compare(other)`, which
returns -1, 0 or 1 from Swift's `<`. Symbol graphs omit the `==` the
compiler synthesizes, so these come from the conformances.

Against the iOS 18 SDK this adds 238 Foundation, 106 SwiftUI, 42
WeatherKit, 20 SwiftData, 6 Charts and 3 Observation callables. Every
module's thunks still type-check.
@NathanWalker
feat(swift-interop): collection protocols take JS arrays
1c568f9
A type parameter constrained to Sequence, Collection or another protocol
arrays conform to now stands in as an array. With
`where C.Element == X` the stand-in is `[X]`; otherwise it is an array
of each of the element's own stand-ins. `C.Element` in the signature is
substituted to match. `some Sequence<Int>` stands in as `[Int]`.

A parameter typed `any Sequence<E>` (or another such protocol) takes an
array, and a result of that type is copied out as one.

Against the iOS 18 SDK this adds 5 Foundation and 2 SwiftUI callables.
Every module's thunks still type-check.
@NathanWalker
NathanWalker force-pushed the swift-interop/5-values branch from 7a3876e to ae7d342 Compare October 21, 2026 16:44
@NathanWalker
NathanWalker force-pushed the swift-interop/6-lifetime-async branch from d43b891 to 1c568f9 Compare October 21, 2026 16:44
@NathanWalker NathanWalker added this to the 10.0 milestone Oct 21, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

10.0

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@NathanWalker
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.