v8 stack 4/5: iOS Swift TurboModule rewrite - #26
Closed
dmurphy5 wants to merge 1 commit into
Closed
Conversation
Replaces the 445-line VydiaRNFileUploader.m with Swift, split across three files: RNBackgroundUpload owns the URLSession work, EventJournal persists terminal outcomes, TaskMap survives the task/upload id mapping across a relaunch. Apple documents taskDescription as non-durable, so it can no longer be the mapping. The two-class split on this platform is forced, not stylistic. The generated spec header is Obj-C++, so the TurboModule shell (RNFileUploader.h/.mm) has to be .mm — but a consumer's AppDelegate is usually plain .m and needs to reach setBackgroundSessionCompletionHandler:forIdentifier:. That handler therefore lives on the Swift class, which plain Obj-C can reach via @import, while every header stays private so the umbrella never exposes Obj-C++ to a .m file. Terminal outcomes are journaled before being emitted, matching Android, so an upload that finishes while the app is dead survives to the next launch. Responses are classified into a typed errorKind and honor acceptStatus. Details worth reviewing: invalidate is identity-checked. React Native dispatches invalidate asynchronously and gives up waiting after ten seconds, so a replacement module can register itself first; clearing unconditionally killed event delivery for the whole process after a slow reload. The module takes its own methodQueue. A TurboModule otherwise shares the process-wide queue, where this module's synchronous journal and task-map disk I/O would stall unrelated native modules and React Native's own invalidate. safeEmit stays synchronous on the URLSession delegate queue — the generated emitter already locks its state and hops to the JS thread — and its try/catch is load-bearing, since emitting before the TurboModule is constructed or with no listeners attached throws std::bad_function_call. Also: cancelUpload uses a lock-guarded Bool rather than a mutable array shared across queues, user-cancel intent is consumed on every terminal outcome so a cancel cannot be resurrected, header values are restricted to String and NSNumber instead of being string-interpolated, progress defaults to 0 rather than -1, and getAllUploads reports real task states. The podspec moves to iOS 15.1, compiles Swift and Obj-C++, and calls install_modules_dependencies for the New Architecture pods. The stale VydiaRNFileUploader.xcodeproj and the unused Helper category are deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
The stack
Five PRs, each based on the one before it. This is 4/5.
ai.openspacerenamemaster#22 stays open and untouched as the single-diff view of the same change.
Its tree and this stack's tip are byte-identical (tree
f8404b0), so mergingeither one produces exactly the same
master. Review whichever is easier tofollow; merge one and close the other.
Replaces the 445-line
VydiaRNFileUploader.mwith Swift, as a TurboModule.Three files:
RNBackgroundUploadowns theURLSessionwork,EventJournalpersists terminal outcomes,
TaskMapsurvives the task↔upload id mapping acrossa relaunch. Apple documents
taskDescriptionas non-durable, so it can no longerbe that mapping.
Terminal outcomes are journaled before being emitted, matching Android, so an
upload that finishes while the app is dead survives to the next launch.
Responses are classified into a typed
errorKindand honoracceptStatus.Why two classes
This split is forced, not stylistic. The generated spec header is Obj-C++, so the
TurboModule shell (
RNFileUploader.h/.mm) has to be.mm. But a consumer'sAppDelegate is usually plain
.mand needs to reachsetBackgroundSessionCompletionHandler:forIdentifier:. That handler thereforelives on the Swift class, which plain Obj-C can reach via
@import, while everyheader stays private so the umbrella never exposes Obj-C++ to a
.mfile.Details worth reviewing
invalidateis identity-checked. React Native dispatchesinvalidateasynchronously and gives up waiting after ten seconds, so a replacement module
can register itself first. Clearing unconditionally killed event delivery for the
whole process after a slow reload.
The module takes its own
methodQueue. A TurboModule otherwise shares theprocess-wide queue, where this module's synchronous journal and task-map disk I/O
would stall unrelated native modules — and React Native's own
invalidate, whichwould queue behind it. The legacy bridge gave every module its own queue; a
TurboModule has to ask.
safeEmitstays synchronous on the URLSession delegate queue: the generatedemitter already locks its state and hops to the JS thread, and deferring to the
main queue would open a window where the TurboModule is torn down before the
block runs. Its
try/catchis load-bearing — emitting before the TurboModule isconstructed, or with no listeners attached, throws
std::bad_function_call.Also:
cancelUploaduses a lock-guardedBoolrather than a mutable arrayshared across queues; user-cancel intent is consumed on every terminal outcome so
a cancel can't be resurrected; header values are restricted to
StringandNSNumberinstead of being string-interpolated;progressdefaults to0ratherthan
-1; andgetAllUploadsreports real task states.The podspec moves to iOS 15.1, compiles Swift and Obj-C++, and calls
install_modules_dependenciesfor the New Architecture pods. The staleVydiaRNFileUploader.xcodeprojand the unusedHelpercategory are deleted.Verified
Built and exercised on a physical iOS device inside Diana: field note create
online and offline, plus attachments. CI doesn't build iOS, and I didn't run a
per-slice iOS build — this content is byte-identical to the branch in #22, which
was device-verified.
Still unverified on device
Background continuation while suspended or terminated; system relaunch on
completion; force-quit producing
cancelReason: 'system'; event delivery after aJS reload.