在Swift中使方法自我弱化

时间:2014-09-01 22:59:31

标签: swift automatic-ref-counting

我有一个需要存储自己方法表的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作为语法糖。

5 个答案:

答案 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) })