我有一个需要存储自己方法表的Swift类。不幸的是,这会导致引用周期,因为它的表通过它存储的方法保留了对self
的引用。
下面的漏洞代码示例:
typealias Callback = ()->()
class CycleInducingClass : NSObject {
var myCallbacks = [Callback]()
override init() {
super.init()
myCallbacks.append(myInternalFunction)
}
func myInternalFunction() {
NSLog("lolol: %d", self.myCallbacks.count)
}
}
到目前为止,我发现的唯一解决方案是改为:
myCallbacks.append({[unowned self] in self.myInternalFunction()})
这很丑陋,容易出错。有更好的想法吗?是否有一些技巧使函数引用本身变弱?即制作myCallbacks
类型的myCallbacks : [WeakCallback]()
数组?据我所知,我甚至无法在上面丑陋的闭包装上构建一个方便函数weaken
作为语法糖。
答案 0 :(得分:13)
你当然可以为此建立一个功能。我不知道它是否会使它变得更好,但它不易出错。
func methodPointer<T: AnyObject>(obj: T, method: (T) -> () -> Void) -> (() -> Void) {
return { [unowned obj] in method(obj)() }
}
...
myCallbacks.append(methodPointer(self, CycleInducingClass.myInternalFunction))
或者,您可以将回调作为方法指针进行管理:
typealias Callback = (CycleInducingClass) -> () -> Void
...
myCallbacks.append(CycleInducingClass.myInternalFunction)
在这种情况下,你需要在调用它们时传递self
(如果你实际上没有这么做的话可能没问题):
self.myCallbacks[0](self)()
所有这一切都基于以下事实:带有签名T
的{{1}}类型的方法等同于带有签名(input) -> (output)
的函数。
如果你很好奇(我是),在这种情况下,覆盖正常工作。因此,如果您继承(T) -> (input) -> (output)
并覆盖CycleInducingClass
,则会调用正确的版本。 (这实际上让我感到惊讶,我还不知道它为什么会起作用,但确实如此。)
编辑:以下是答案:https://devforums.apple.com/message/1036509#1036509
答案 1 :(得分:1)
Robs的回答对我有用。我确实将它重构为更多OO,所以我想我会在这里分享它,以防它帮助其他人:
public protocol WeakCallback{
func invoke()
}
public class WeakCallbackInstance<T: AnyObject> : WeakCallback{
private let callback: ()->Void
private weak var target: T?
public init(target: T, action: (T)->()->Void){
self.target = target
callback = { [weak target] in
action(target!)()
}
}
public func invoke(){
callback()
}
}
class ExampleUsage{
func usage(){
var callbacks = [WeakCallback]()
let one = WeakCallbackInstance(target: DummyCallbackOne(), action:DummyCallbackOne.callbackOne)
let two = WeakCallbackInstance(target: DummyCallbackTwo(), action:DummyCallbackTwo.callbackTwo)
callbacks.append(one)
callbacks.append(two)
callbacks.first?.invoke()
}
}
class DummyCallbackOne{
func callbackOne(){
}
}
class DummyCallbackTwo{
func callbackTwo(){
}
}
答案 2 :(得分:0)
在Swift 4中(我不确定语法何时可用),只需执行{ [weak self] (params) in }
即可使self
弱。它基本上是[unowned self]
,Self?
到Self!
。编译器甚至需要self?.foo
而不是self.foo
。
答案 3 :(得分:0)
在Swift 5.2中,callAsFunction
为此提供了很好的语法,直到参数标签开始起作用。
public struct WeakMethod<Reference: AnyObject, Input, Output> {
public init(
reference: Reference?,
method: @escaping Method
) {
self.reference = reference
self.method = method
}
public weak var reference: Reference?
public var method: Method
}
public extension WeakMethod {
struct ReferenceDeallocatedError: Error { }
typealias Method = (Reference) -> (Input) -> Output
/// - Throws: ReferenceDeallocatedError
func callAsFunction(_ input: Input) throws -> Output {
guard let reference = reference
else { throw ReferenceDeallocatedError() }
return method(reference)(input)
}
}
public extension WeakMethod where Input == () {
init(
reference: Reference?,
method: @escaping (Reference) -> () -> Output
) {
self.reference = reference
self.method = { reference in
{ _ in method(reference)() }
}
}
/// - Throws: ReferenceDeallocatedError
func callAsFunction() throws -> Output {
try self( () )
}
}
final class WeakMethodTestCase: XCTestCase {
func test_method() throws {
var reference: Reference? = Reference()
let assign1234 = WeakMethod(reference: reference, method: Reference.assign1234)
try assign1234()
XCTAssertEqual(reference?.property, 1234)
reference = nil
XCTAssertThrowsError( try assign1234() ) {
XCTAssert($0 is WeakMethod<Reference, (), Void>.ReferenceDeallocatedError)
}
}
func test_closure_noParameters() throws {
var reference: Reference? = Reference()
let assign1234 = WeakMethod(reference: reference) {
reference in { reference.property = 1234 }
}
try assign1234()
XCTAssertEqual(reference?.property, 1234)
reference = nil
XCTAssertThrowsError( try assign1234() ) {
XCTAssert($0 is WeakMethod<Reference, (), Void>.ReferenceDeallocatedError)
}
}
func test_closure_1Parameter() throws {
var reference: Reference? = Reference()
let assign = WeakMethod(reference: reference) {
reference in { reference.property = $0 }
}
try assign(1234)
XCTAssertEqual(reference?.property, 1234)
reference = nil
XCTAssertThrowsError( try assign(1234) ) {
XCTAssert($0 is WeakMethod<Reference, Int, Void>.ReferenceDeallocatedError)
}
}
}
private final class Reference {
var property = 1
func assign1234() {
property = 1234
}
}
不幸的是,随着您继续添加参数,您将需要继续添加初始化器+方法对,就像这样:
init<Input0, Input1>(
reference: Reference?,
method: @escaping (Reference) -> (Input0, Input1) -> Output
)
where Input == (Input0, Input1) {
self.reference = reference
self.method = { reference in
{ method(reference)($0.0, $0.1) }
}
}
/// - Throws: ReferenceDeallocatedError
func callAsFunction<Input0, Input1>(_ input0: Input0, _ input1: Input1) throws -> Output
where Input == (Input0, Input1) {
try self( (input0, input1) )
}
答案 4 :(得分:-1)
用block
包装没有param函数myCallbacks.append({ [unowned self] in self.myInternalFunction() })
使用块
包装param函数myCallbacks.append({ [unowned self] page in self.reloadData(page: page) })