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) )
}
let myInternalFunction = {[unowned self] in ...}
that works, but is also rather ugly. – Epilate