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