kylehughes/weakify
*A simple, ergonomic solution for safely capturing method references in Swift.*
About
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
weakorunownedcaptures.
Weakify is extremely lightweight and has a robust test suite.
Capabilities
- [x] Weakly capture method references with automatic fallback values.
- [x] Unownedly capture method references when you know the target will outlive the closure.
- [x] Ergonomic handling of heterogenous arguments.
- [x] Swift 6 language mode support.
Supported Platforms
- iOS 13.0+
- macOS 10.15+
- tvOS 13.0+
- visionOS 1.0+
- watchOS 6.0+
Requirements
- 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
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/kylehughes/Weakify.git", .upToNextMajor(from: "1.0.0")),
]Quick Start
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")
}
}Usage
[!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.layoutIfNeededorArray.append. Its curried shape is(Self) -> (Args…) -> Result, so supplying a specific instance—unappliedMethodReference(target)—yields the regular(Args…) -> Resultclosure you would normally call.
Weak Capture with Fallback
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")Weak Capture without Fallback
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)Unowned Capture
Use disown to capture self when the target object will definitely outlive the closure:
let unownedHandler = disown(MyViewController.updateStatus, on: self)Heterogeneous Arguments
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)Important Behavior
weakifyclosures evaluate the provided default when the target is deallocated.disownclosures will crash if called after the target is deallocated; ensure the target outlives the closure.
Contributions
Weakify is not accepting source contributions at this time. Bug reports will be considered.
License
Weakify is available under the MIT license.
See LICENSE for details.
Package Metadata
Repository: kylehughes/weakify
Default branch: main
README: README.md