如何在 Swift 中使用目标和 CADisplayLink 实例之间的弱引用设置 CADisplayLink
How to set CADisplayLink in Swift with weak reference between target and CADisplayLink instance
在Objective-C中,我们可以用代理模式初始化CADisplayLink来打破强引用:
WeakProxy *weakProxy = [WeakProxy weakProxyForObject:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
然后,将dealloc
中的displayLink作废即可:
- (void)dealloc
{
[_displayLink invalidate];
}
但是,NSProxy 似乎无法在 Swift 中继承:https://bugs.swift.org/browse/SR-1715
我试着这样写:
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayDidRefresh(dpLink:)))
没用。
我想知道是否有任何方法可以像 Objective-C 那样实现。
更好的方法可能是使 link 中的显示无效
viewWill/DidDisappear
,另见
获取有用信息。
如果这不是一个选项: 使代理对象继承自 NSObject
而不是 NSProxy
。例如 Objective-C 解决方案
此处给出
- CADisplayLink at iOS 6.0 not retaining target
并且可以很容易地转换为 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
}
}
然后可以用作
displayLink = CADisplayLink(target: JAWeakProxy(target: self),
selector: #selector(didRefresh(dpLink:)))
你的方法
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayDidRefresh(dpLink:)))
不起作用,因为它在 CADisplayLink
时展开 weakSelf
已初始化并传递对 self
的强引用作为目标。
这个代理 class 应该可以正常工作。不要忘记在 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
}
}
用法:
class ViewController: UIViewController {
var displaylinkProxy: CADisplayLinkProxy?
override func viewDidLoad() {
super.viewDidLoad()
displaylinkProxy = CADisplayLinkProxy(handle: { [weak self] in
self?.updateAnything()
})
}
@objc func updateAnything() {
print(Date())
}
}
另一个解决方案,这个解决方案从其外部 API 隐藏代理/objc 运行时。
只要 DisplayLink
被变量引用,它就会一直存在。
一旦变量超出范围或设置为 nil,CADisplayLink 就会失效,因此也可以取消初始化目标。
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)
}
}
在Objective-C中,我们可以用代理模式初始化CADisplayLink来打破强引用:
WeakProxy *weakProxy = [WeakProxy weakProxyForObject:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
然后,将dealloc
中的displayLink作废即可:
- (void)dealloc
{
[_displayLink invalidate];
}
但是,NSProxy 似乎无法在 Swift 中继承:https://bugs.swift.org/browse/SR-1715
我试着这样写:
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayDidRefresh(dpLink:)))
没用。
我想知道是否有任何方法可以像 Objective-C 那样实现。
更好的方法可能是使 link 中的显示无效
viewWill/DidDisappear
,另见
获取有用信息。
如果这不是一个选项: 使代理对象继承自 NSObject
而不是 NSProxy
。例如 Objective-C 解决方案
此处给出
- CADisplayLink at iOS 6.0 not retaining target
并且可以很容易地转换为 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
}
}
然后可以用作
displayLink = CADisplayLink(target: JAWeakProxy(target: self),
selector: #selector(didRefresh(dpLink:)))
你的方法
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayDidRefresh(dpLink:)))
不起作用,因为它在 CADisplayLink
时展开 weakSelf
已初始化并传递对 self
的强引用作为目标。
这个代理 class 应该可以正常工作。不要忘记在 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
}
}
用法:
class ViewController: UIViewController {
var displaylinkProxy: CADisplayLinkProxy?
override func viewDidLoad() {
super.viewDidLoad()
displaylinkProxy = CADisplayLinkProxy(handle: { [weak self] in
self?.updateAnything()
})
}
@objc func updateAnything() {
print(Date())
}
}
另一个解决方案,这个解决方案从其外部 API 隐藏代理/objc 运行时。
只要 DisplayLink
被变量引用,它就会一直存在。
一旦变量超出范围或设置为 nil,CADisplayLink 就会失效,因此也可以取消初始化目标。
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)
}
}