Skip to content

Fixed certain issues with the Swift wrapper - #649

Merged
TimOliver merged 5 commits into
mainfrom
split/3-object-lifetime
Jul 28, 2026
Merged

Fixed certain issues with the Swift wrapper#649
TimOliver merged 5 commits into
mainfrom
split/3-object-lifetime

Conversation

@TimOliver

Copy link
Copy Markdown
Owner

No description provided.

…llation

The scheduled reset timer used the target/selector form, which retains
its target, so dismissing the controller mid-adjustment kept the crop
view and both full-size image copies alive until the timer fired. It
now uses the block form with a weak reference and is invalidated on
dealloc. Touch and gesture cancellation (incoming calls, system alerts)
also restart the reset timer instead of leaving the view stuck in its
editing appearance.
The closure handlers captured the unwrapped delegate strongly, so the
nominally weak delegate was retained for the controller's lifetime --
and in the common pattern where the delegate owns the controller, the
resulting cycle leaked both, along with the full-resolution image. The
closures now resolve the delegate through self at call time, which
also stops a replaced delegate from continuing to receive callbacks.
- The bridging closures capture the delegate weakly, honouring the weak
  contract of the delegate property, and fall back to the default dismissal
  if it has been deallocated before a callback fires
- Closures the host assigned directly are no longer cleared for selectors the
  delegate doesn't implement; the bridge tracks which slots it owns so that
  replacing the delegate tears down only its own
- main's cropViewControllerDidTapDone bridge is folded into the same scheme.
  It gets no dismissal fallback, since it fires alongside the crop callbacks
  rather than instead of them, so those still own the dismissal
`CropViewController.swift` had no automated coverage at all, so the closure
bridge it maintains over the Objective-C delegate -- the part of this library
with the most intricate lifetime rules, and the source of two of the bugs
fixed in 3.1.2 -- was only ever verified by hand.

Rather than introduce a second test target and a second CI scheme, the
existing TOCropViewControllerTests bundle now also compiles
CropViewController.swift and a Swift test class, which is how both example
apps consume the wrapper: sources compiled in directly, with the
Objective-C layer exposed through a bridging header. `fastlane test` picks
the new cases up with no change to the workflow.

Eleven cases, covering: per-selector installation of the bridging closures;
tear-down when the delegate is replaced or cleared; survival of closures the
host assigned directly; the weak delegate not being retained; tolerance of a
delegate that has been deallocated; forwarding of the ~20 wrapped properties
to the underlying controller; and the child view controller relationship.
Reverting the delegate-bridge fix from the previous commit fails three
assertions across two of them, so they do bind to the behaviour they cover.

One thing the exercise turned up but which isn't fixed here: TOCropView.h
forward-declares TOCropOverlayView rather than importing it, and Swift drops
properties whose class is incomplete, so `cropView.gridOverlayView` is
invisible to Swift wherever these headers are consumed without a module.
Importing it from the public header breaks the framework target's module
verifier, because that target publishes no headers at all -- untangling that
is its own change, so the test bridging header imports it locally for now.
Base automatically changed from split/2-rotation-geometry to main July 28, 2026 14:58
@TimOliver
TimOliver merged commit e518837 into main Jul 28, 2026
1 check passed
@TimOliver
TimOliver deleted the split/3-object-lifetime branch July 28, 2026 14:58
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.

1 participant