Skip to content

refactor(ios): serve media uploads from a handler object, not statics - #631

Draft
jkmassel wants to merge 2 commits into
refactor/media-processor-renamefrom
refactor/media-upload-handler-object
Draft

refactor(ios): serve media uploads from a handler object, not statics#631
jkmassel wants to merge 2 commits into
refactor/media-processor-renamefrom
refactor/media-upload-handler-object

Conversation

@jkmassel

@jkmassel jkmassel commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Stacked on #630. Eighth of ten PRs splitting #621. No behavior change, and no test changed — the review is "did any body change?"

What?

Two commits:

1. HTTPRequestHandler in GutenbergKitHTTP

An HTTPServer.start overload taking a handler object rather than a closure. Purely additive: the closure overload is unchanged and forwards to the same code path, so no existing caller, test, or the debug server is affected.

2. MediaUploadServer serves from one

The statics threading an UploadContext become a Handler struct with stored properties.

Why?

The closure form of start can't capture the object that owns the server: the closure has to exist before the server does, and retrofitting self would form MediaUploadServer → HTTPServer → handler → MediaUploadServer, so the owner's deinit — and its stop() — would never run. A consumer with dependencies to hold therefore ends up with static functions threading a context parameter through every call, which is how MediaUploadServer is written today.

HTTPRequestHandler is deliberately not AnyObject-constrained: a struct conformer cannot participate in a reference cycle at all, so the ownership question doesn't arise. A final class works too, under the same leaf discipline HTTPServerDelegate already documents.

Request handling is mandatory, so it can't be a defaulted HTTPServerDelegate method the way optional customization points are — hence an overload rather than a new delegate requirement.

How?

handleRequest becomes handle; the functions that use the dependencies become instance methods, and the ones that don't (attachmentId, relayResponse, uploadErrorResponse, formFields) stay static. UploadContext goes away — Handler is what it was. Helpers outside the handler (errorResponse, writeStream, sanitizeFilename, uploadsTempDirectory) are qualified rather than moved.

The restructure commit touches one file and changes no test, which is the point: it is 278/277 lines of churn that would otherwise sit in the middle of an API-design diff.

Testing Instructions

  • swift test — host suite green, unchanged
  • Two new tests for the HTTPServer overload
  • iOS Simulator xcodebuild
  • SwiftLint clean

The closure form of `start` can't capture the object that owns the
server: the closure has to exist before the server does, and retrofitting
`self` would form `owner -> HTTPServer -> handler -> owner`, so the
owner's deinit — and its `stop()` — would never run. A consumer with
dependencies to hold therefore ends up with static functions threading a
context parameter through every call, which is how MediaUploadServer is
written today.

Add an `HTTPRequestHandler` protocol and a `start` overload that takes
one. The dependencies become stored properties and the request logic
becomes instance methods. The protocol is deliberately not
`AnyObject`-constrained: a struct conformer cannot participate in a
reference cycle at all, so the ownership question doesn't arise. A final
class works too, under the same leaf discipline HTTPServerDelegate
already documents.

The closure overload is unchanged and forwards to the same code path, so
this is purely additive — no existing caller, test, or the debug server
is affected. Request handling is mandatory, so it can't be a defaulted
HTTPServerDelegate method the way optional customization points are;
hence an overload rather than a new delegate requirement.
`MediaUploadServer` handled requests through static functions threading an
`UploadContext` parameter through every call, because the closure form of
`HTTPServer.start` can't capture the object that owns the server: the
closure has to exist before the server does, and capturing `self` would
form `MediaUploadServer -> HTTPServer -> handler -> MediaUploadServer`, so
`deinit` — and its `stop()` — would never run.

The previous commit added `HTTPRequestHandler` for exactly this. The
dependencies become stored properties on a `Handler` struct and the
request logic becomes instance methods; a value type can't participate in
a reference cycle, so the ownership question doesn't arise.

Mechanically: `handleRequest` becomes `handle`, the functions that use the
dependencies become instance methods, and the ones that don't
(`attachmentId`, `relayResponse`, `uploadErrorResponse`, `formFields`)
stay static. `UploadContext` goes away — `Handler` is what it was. Helpers
outside the handler (`errorResponse`, `writeStream`, `sanitizeFilename`,
`uploadsTempDirectory`) are qualified rather than moved.

No behavior change: only this file is touched, and no test changed.
@jkmassel jkmassel added [Type] Task Issues or PRs that have been broken down into an individual action to take iOS labels Sep 5, 2026
@jkmassel jkmassel self-assigned this Sep 5, 2026
@wpmobilebot

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/631")

Built from 5783bdb

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

Labels

iOS [Type] Task Issues or PRs that have been broken down into an individual action to take

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants