2

Say you have ...

class Boss {
   func callback() { }
}

and you have something like ...

class Worker {
   static func compute(forMe: Boss) {
      .. do some work that takes 10 seconds .. 
      forMe?.callback()
   }
}

So: Somewhere in a particular instance of Boss, let's say b, you call something like ...

 Worker.compute(forMe: self)

Say the 10 seconds passes. Say at 7 seconds, b ceases to exist (well, I want it to cease to exist). At the end of the 10 seconds, I want compute to say, "Oh, damn, it seems that forMe no longer exists. I will not do the callback, and give up."

So, the "forMe" inside "compute" should be weak. (I guess?)

Really, how do you achieve this basic concept in Swift?

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • 3
    Assuming the compute function is doing something asynchronous with a closure, you would capture the parameter weakly in that closure's capture list – dan Mar 24 '20 at 16:13
  • 1
    you oughta give an example of that for, uh, other people @dan ! :) – Fattie Mar 24 '20 at 17:28
  • 1
    It is impossible for `forMe` to become nil unless you do something asynchronous, because arguments are captured strongly. –  Mar 24 '20 at 19:31

3 Answers3

1

The best I could find was this answer

Basically it consists of creating your own Weak struct, because arguments can only be "strong" in Swift, at the moment.

public struct Weak<T> where T: AnyObject {
    public weak var object: T?

    public init(_ object: T?) {
        self.object = object
    }
}

 func compute(forMe: Weak<Boss>) {        
    if let boss = forMe.object {
        boss.callback()
    }
    else {
        print("Oh, damn, it seems that forMe no longer exists. I will not do the callback, and give up.")
    }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
Xys
  • 8,486
  • 2
  • 38
  • 56
1

If your compute function is doing work asynchronously and getting a callback then it can capture your parameter weakly in that work's closure:

static func compute(forMe: Boss) {
    doSomethingAsync { [weak forMe] in
        // forMe will be nil in here if it was deallocated before this is called
        forMe?.callback()
    }
}
dan
  • 9,695
  • 1
  • 42
  • 40
1

dan probably offered the simplest answer for your use case.

If you ever need to store a "weak method", though, you can use this:

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

// MARK:-
  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) )
  }
}

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( () )
  }
}
var boss: Boss? = .init()
let callBack = WeakMethod(reference: boss, method: Boss.callBack)
try callBack()
boss = nil
try callBack() // ReferenceDeallocatedError

I capitalized the "B" because callBack is a predicate (verb + object), not a noun. I advise against using it in any form, though. "callback" is a term from antiquity that isn't used in Swift.

Note: you can also create your own "methods" using closures, if you have a reason for it.

final class WeakMethodTestCase: XCTestCase {
  func test_method_noParameters() 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_method_2Parameters() throws {
    var reference: Reference? = Reference()

    let assignSum = WeakMethod(reference: reference, method: Reference.assignSum)

    try assignSum(2, 3)
    XCTAssertEqual(reference?.property, 5)

    reference = nil
    XCTAssertThrowsError( try assignSum(2, 3) ) {
      XCTAssert($0 is WeakMethod<Reference, (Int, Int), 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
  }

  func assignSum(int0: Int, int1: Int) {
    property = int0 + int1
  }
}