In Objective-C, we can init CADisplayLink with Proxy Pattern to break strong reference:
WeakProxy *weakProxy = [WeakProxy weakProxyForObject:self];
self.displayL
Another solution, this one hides the proxy / objc runtime from its external API.
The DisplayLink
stays alive for as long as it is referenced by variable.
Once the variable goes out of scope or is set to nil, the CADisplayLink is invalidated so that the target can be deinited also.
import Foundation
import UIKit
/// DisplayLink provides a block based interface for CADisplayLink.
/// The CADisplayLink is invalidated upon DisplayLink deinit.
///
/// Usage:
/// ```
/// let displayLink = DisplayLink { caDisplayLink in print("Next frame scheduled \(caDisplayLink.targetTimestamp)") }
/// ```
///
/// Note: Keep a reference to the DisplayLink.
final class DisplayLink {
let displayLink: CADisplayLink
init(runloop: RunLoop? = .main, prepareNextFrame: @escaping (CADisplayLink) -> ()) {
displayLink = CADisplayLink(
target: DisplayLinkTarget(prepareNextFrame),
selector: #selector(DisplayLinkTarget.prepareNextFrame))
if let runloop = runloop {
displayLink.add(to: runloop, forMode: .default)
}
}
deinit {
displayLink.invalidate()
}
}
private class DisplayLinkTarget {
let callback: (CADisplayLink) -> ()
init(_ callback: @escaping (CADisplayLink) -> ()) {
self.callback = callback
}
@objc func prepareNextFrame(displaylink: CADisplayLink) {
callback(displaylink)
}
}
An better approach might be to invalidate the display link in
viewWill/DidDisappear
, see also
for useful information.
If that is not an option: Make the proxy object inherit from NSObject
instead of NSProxy
. An Objective-C solution is for example
given here
and that can easily be translated to Swift 3:
class JAWeakProxy: NSObject {
weak var target: NSObjectProtocol?
init(target: NSObjectProtocol) {
self.target = target
super.init()
}
override func responds(to aSelector: Selector!) -> Bool {
return (target?.responds(to: aSelector) ?? false) || super.responds(to: aSelector)
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
}
}
which can then be used as
displayLink = CADisplayLink(target: JAWeakProxy(target: self),
selector: #selector(didRefresh(dpLink:)))
Your approach
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayDidRefresh(dpLink:)))
does not work because it unwraps weakSelf
when the CADisplayLink
is initialized and passes a strong reference to self
as the target.
This proxy class should just work. Don't forget to invalidate before the dealloc.
import UIKit
class CADisplayLinkProxy {
var displaylink: CADisplayLink?
var handle: (() -> Void)?
init(handle: (() -> Void)?) {
self.handle = handle
displaylink = CADisplayLink(target: self, selector: #selector(updateHandle))
displaylink?.add(to: RunLoop.current, forMode: .common)
}
@objc func updateHandle() {
handle?()
}
func invalidate() {
displaylink?.remove(from: RunLoop.current, forMode: .common)
displaylink?.invalidate()
displaylink = nil
}
}
Usage:
class ViewController: UIViewController {
var displaylinkProxy: CADisplayLinkProxy?
override func viewDidLoad() {
super.viewDidLoad()
displaylinkProxy = CADisplayLinkProxy(handle: { [weak self] in
self?.updateAnything()
})
}
@objc func updateAnything() {
print(Date())
}
}