27

I have a Swift class that needs to store a table of its own methods. Unfortunately this is causing a reference cycle, because its table retains references to self via the methods it stores.

Example leaky code below:

typealias Callback = ()->()

class CycleInducingClass : NSObject {
    var myCallbacks = [Callback]()  

    override init() {
        super.init()
        myCallbacks.append(myInternalFunction)
    }

    func myInternalFunction() {
        NSLog("lolol: %d", self.myCallbacks.count)
    }
}

The only solution I've found so far is to instead do this:

myCallbacks.append({[unowned self] in self.myInternalFunction()})

That's pretty ugly, and prone to error. Any better ideas? Is there some trick for making the function references themselves be weak? i.e. to make the myCallbacks array of type myCallbacks : [WeakCallback]() or something? As far as I can tell I can't even build a convenience function weaken as syntactic sugar over the ugly closure wrapper above.

2
  • how about adding a parameter to myInternalFunction? that could be declared weak.....not pretty though
    – Daij-Djan
    Sep 1, 2014 at 23:07
  • I've also tried just making all my internal functions closures, i.e. let myInternalFunction = {[unowned self] in ...} that works, but is also rather ugly.
    – wxs
    Sep 3, 2014 at 16:48

5 Answers 5

13

You can certainly build a function for this. I don't know if it makes it dramatically better, but it is less error-prone.

func methodPointer<T: AnyObject>(obj: T, method: (T) -> () -> Void) -> (() -> Void) {
  return { [unowned obj] in method(obj)() }
}
...
myCallbacks.append(methodPointer(self, CycleInducingClass.myInternalFunction))

Alternately, you could manage your callbacks as method pointers:

typealias Callback = (CycleInducingClass) -> () -> Void
...
myCallbacks.append(CycleInducingClass.myInternalFunction)

In that case, you'd need to pass self when you called them (which may be fine if you don't actually do this a lot):

self.myCallbacks[0](self)()

All of this is based on the fact that a method on type T with signature (input) -> (output) is equivalent to a function with the signature (T) -> (input) -> (output).

In case you're curious (I was), overriding works correctly in this case. So if you subclass CycleInducingClass and override myInternalFunction, the correct version will be called. (That actually surprises me a little, and I don't yet know exactly why it works, but it does.)

EDIT: Here's the answer to that: https://devforums.apple.com/message/1036509#1036509

2
  • Ah interesting, I didn't realize that you statically access methods and they would give you a function mapping instances of your class to the class method. That's the ingredient I was missing. Thanks!
    – wxs
    Sep 8, 2014 at 19:25
  • 2
    What if the function takes parameters? How would that work?
    – Snowman
    Dec 10, 2015 at 18:28
3

Robs answer worked for me. I did refactor it to be a little more OO though so I thought I would share it here in case it helps someone else:

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(){
    }
}
0

In Swift 4 (I am not sure when the syntax became available), simply do { [weak self] (params) in } to make self weak. It basically is to [unowned self], what Self? is to Self!. The compiler even requires self?.foo instead of simply self.foo.

0

With Swift 5.2, callAsFunction allows for nice syntax for this, until argument labels come into play.

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
  }
}

Unfortunately, as you keep adding parameters, you'll need to keep adding initializer+method pairs, like so:

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) )
}
-1

wrapped no param function with block

myCallbacks.append({ [unowned self] in self.myInternalFunction() })

wrapped param function with block

myCallbacks.append({ [unowned self] page in self.reloadData(page: page) })

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.