A simple, ergonomic solution for safely capturing method references in Swift.
Weakify provides convenient and safe mechanisms for capturing method references weakly or unownedly, ensuring closures referencing methods on an object do not inadvertently extend the object's lifetime. It relies on unapplied method references, and uses parameter packs to support heterogeneous argument lists.
This package encourages:
- Single-line syntax for capturing method references.
- Avoidance of retain cycles through
weak
orunowned
captures.
Weakify is extremely lightweight and has a robust test suite.
- Weakly capture method references with automatic fallback values.
- Unownedly capture method references when you know the target will outlive the closure.
- Ergonomic handling of heterogenous arguments.
- Swift 6 language mode support.
- iOS 13.0+
- macOS 10.15+
- tvOS 13.0+
- visionOS 1.0+
- watchOS 6.0+
- Swift 6.1+
- Xcode 16.3+
Note
This package ideally would only require Swift 5.9, but compiler versions prior to 6.1 (packaged with Xcode 16.3)
have a bug that makes the intended usage incompatible with @MainActor
-isolated closures. We require Swift language
tools version 6.1 as a proxy for this tribal knowledge.
Documentation is available on GitHub Pages.
dependencies: [
.package(url: "https://github.com/kylehughes/Weakify.git", .upToNextMajor(from: "1.0.0")),
]
Weakly capture a method reference:
import Weakify
class MyViewController: UIViewController {
private lazy var button: UIButton = {
let button = UIButton()
button.addAction(
UIAction(handler: weakify(MyViewController.buttonTapped, on: self)),
for: .primaryActionTriggered
)
return button
}()
private func buttonTapped(_ action: UIAction) {
print("Button tapped")
}
}
Unownedly capture a method reference:
import Weakify
class MyViewController: UIViewController {
func observe(notificationCenter: NotificationCenter) {
notificationCenter.addObserver(
forName: NSNotification.Name("MyNotification"),
object: nil,
queue: .main,
using: disown(MyViewController.handleNotification, on: self)
)
}
private func handleNotification(_ notification: Notification) {
print("Notification received")
}
}
Note
An unapplied method reference (UMR) is the function value produced by writing an instance method on the type
instead of an instance, for example UIView.layoutIfNeeded
or Array.append
. Its curried shape is
(Self) -> (Args…) -> Result
, so supplying a specific instance—unappliedMethodReference(target)
—yields the regular
(Args…) -> Result
closure you would normally call.
Use weakify
to safely capture self
without retaining it. A default fallback value can be provided for when self
has been deallocated:
let weakHandler = weakify(MyViewController.formatMessage, on: self, default: "N/A")
Use weakify
to safely capture self
without retaining it. If the accepting closure does not need a return value, you can omit the fallback. No side effects will occur if the target is deallocated.
let weakHandler = weakify(MyViewController.fireAndForget, on: self)
Use disown
to capture self
when the target object will definitely outlive the closure:
let unownedHandler = disown(MyViewController.updateStatus, on: self)
Weakify supports methods with heterogeneous argument lists:
func printMessage(_ prefix: String, count: Int) {
print("\(prefix): \(count)")
}
let printer = weakify(MyViewController.printMessage, on: self)
printer("Age", 5)
weakify
closures evaluate the provided default when the target is deallocated.disown
closures will crash if called after the target is deallocated; ensure the target outlives the closure.
Weakify is not accepting source contributions at this time. Bug reports will be considered.
Weakify is available under the MIT license.
See LICENSE
for details.