Skip to content

Keep @objc only where the Objective-C runtime needs it - #42

Merged
Mx-Iris merged 5 commits into
mainfrom
refactor/host-objc-cleanup
Oct 15, 2026
Merged

Mx-Iris merged 5 commits into
mainfrom
refactor/host-objc-cleanup

Conversation

@Mx-Iris

@Mx-Iris Mx-Iris commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Swift host kept most of the @objc of the Objective-C host it replaced. Swift code calls those members directly, so the attribute only added Objective-C metadata and a thunk per member. This removes 412 of the host's 630 @objc, turns the five places that still reached a member by name into direct calls, and deletes the members left without callers. Nothing changes for users.

The 218 that remain are there because the Objective-C runtime has to find the member:

  • #selector targets: actions, notification observers, MessageAttribute subscribers, BaseControl and TipsView click actions
  • properties observed with KVO or animated through animator() (@objc dynamic)
  • methods AppKit calls by name without a formal protocol, such as AppWindow's drag-and-drop methods
  • optional requirements of @objc protocols, which Swift requires to be marked @objc
  • the types those declarations use

Apart from these, only the three document classes Info.plist lists under NSDocumentClass keep Objective-C names.

The compiler enforces the first item and the last two. Remove @objc from a KVO property or a drag-and-drop method and the app still builds; then observe(\.x) crashes, animator() stops animating, or a dropped file no longer opens.

Commits

Each commit builds on its own.

  1. Release check. check-host-release-clean.sh still looked for the DEBUG-only types in LookInside/DebugE2E/ under the names they had before the rename, which no binary contains any more; it now uses their Swift names. The new Scripts/test-check-host-release-clean.sh, run by the build workflow, fails when one of those types is renamed and the check is not.
  2. Drop @objc from members only Swift uses: 405 declarations in 86 files, including the dynamic of nine properties nothing observes, @objc on ten enums, and the Objective-C names of AppDelegate and DanceScriptManager.
  3. Call the members read by name directly: three value(forKey:) reads in the DEBUG UI snapshots, a perform(_:with:) in the Dashboard, and the optionalAppropriateScreenshot wrapper, which goes.
  4. Drop the redundant @objc on two initializers that override @objc ones and are inferred @objc anyway.
  5. Delete members nothing calls, most of them Objective-C entry points for callers that are gone.

Testing

  • xcodebuild Debug build of every commit, with no new warnings. A Release build of the last commit passes Scripts/check-host-release-clean.sh --local-build.
  • All nine Scripts/test-*.sh pass.
  • Each commit's build was compared with the previous commit's on the Objective-C side: the @objc thunks in the binary (from nm, demangled) and the method and ivar lists of every class, category and protocol. Commit 2 removes 489 thunks, each for a member it changes or an override that inherited @objc from one; commit 3 removes exactly its five; commit 4 changes nothing; commit 5 removes only LaunchWindowController's -init and the ivars of the two deleted stored properties. No commit adds anything.

Not yet checked by hand in the running app:

  • The hierarchy table scrolls.
  • Dropping a .lookin file on a window opens it.
  • The View menu's zoom and expand-level items work in a live inspection window and in a window opened from a file.

When the host's types lost their LK prefixes and Objective-C names, the
DEBUG-only end-to-end dump and UI snapshots in LookInside/DebugE2E/
became DebugE2EDump, DebugUISnapshots, DebugWindowImage and
SnapshotNormalizer. check-host-release-clean.sh kept looking for
LKDebugE2EDump, LKDebugUISnapshots, LKDebugWindowImage and
LookinSnapshotNormalizer, names no binary contains any more, so that
part of the check could no longer fail. It now looks for the new names.

Scripts/test-check-host-release-clean.sh, which the build workflow now
runs, keeps the check and the code in step. For every file in
LookInside/DebugE2E/ it builds a throwaway app bundle that declares the
file's types and expects the check to reject it, both with a symbol
table and stripped; a bundle of ordinary host types must pass. Renaming
a DebugE2E type without updating the check fails the test.

Comments that still named the old types are updated as well.
The Swift host kept the @objc of the Objective-C host it replaced, but
most of those members are now called only from Swift, which calls them
directly rather than through the Objective-C runtime. The attribute
only added Objective-C metadata and a thunk per member.

This removes it from 405 declarations in 86 files: methods,
initializers, properties, and explicit selectors written as
@objc(name:). Nine of the properties were also dynamic although nothing
observes them with KVO or animates them, and lose that too. Ten enums
are no longer @objc, and AppDelegate and DanceScriptManager give up
their Objective-C names, which nothing looks up.

@objc stays where the Objective-C runtime has to find the member:
#selector targets, properties observed with KVO or animated through
animator(), methods AppKit calls by name such as the window's
drag-and-drop methods, optional requirements of @objc protocols, the
types those declarations use, and the three document classes
Info.plist names.

In a Debug build, 489 @objc thunks go away, each for one of these
members or an override that inherited @objc from it, and none is
added.
Five host members were reached through the Objective-C runtime from
Swift code. The DEBUG UI snapshots read StaticViewController's
dashboardController, DashboardViewController's scrollView and
HierarchyView's searchTextFieldView with value(forKey:); the Dashboard
sent updateAfterModifyingDisplayItem: to StaticAsyncUpdateManager with
perform(_:with:); and DisplayItem's optionalAppropriateScreenshot read
appropriateScreenshot with value(forKey:), because the Objective-C
header the method used to be declared in promised a non-nil image.

All five are now ordinary property reads and calls, so the members
lose @objc. optionalAppropriateScreenshot goes: appropriateScreenshot()
is a Swift method now, and its optional result already says it can be
nil.

In a Debug build, exactly these five @objc thunks go away.
TableView's init(frame:) and StaticAsyncUpdateManager's init() override
@objc initializers of NSScrollView and NSObject, so Swift makes them
@objc whether or not they say so. The built app's Objective-C metadata
is the same before and after.
Nothing in the host uses these any more; most were kept as Objective-C
entry points for callers that no longer exist:

- VersionComparer.compare(newest:user:), which only forwarded to
  compare(expectedVersion:realVersion:)
- LiveDocumentController's completion-handler form of
  openLiveDocument(for:)
- PreferenceManager's callStackType and the PreferredCallStackType enum
- PrivateDiscriminatorStore.isFeatureEnabled
- PrivateDiscriminatorDashboardPayload.displayClassName, and the
  className of the descriptor it was filled from
- LaunchViewController's init(window:) and LaunchWindowController's
  init(), shorthands for autoEnterOnInitialReload: false

In a Debug build, LaunchWindowController's -init and the ivars of the
two removed stored properties leave the Objective-C metadata; nothing
else there changes.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 00:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Mx-Iris
Mx-Iris merged commit 9b87293 into main Oct 15, 2026
2 checks passed
@Mx-Iris
Mx-Iris deleted the refactor/host-objc-cleanup branch October 15, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants