如何将闭包附加到另一个闭包?

How to append a closure to another closure?

我的第一个闭包在这里:

var instructions: (() -> Void) = {
    print("First")
}

instructions() /// prints "First"

现在我有另一个闭包:

let additionalInstructions: (() -> Void) = {
    print("Second")
}

additionalInstructions() /// prints "Second"

我想 “追加” additionalInstructionsinstructions 的末尾...这可能吗?我尝试制作一个包含它们的新闭包,如下所示:

let finalInstructions: (() -> Void) = {
    instructions()
    additionalInstructions()
}

finalInstructions()

这会打印

First

Second

但是,当我用 finalInstructions 替换 instructions 时,我得到一个 EXC_BAD_ACCESS.

instructions = finalInstructions
instructions() /// error here

我认为这是因为闭包是引用类型,当 instructions 包含自身时会发生某种循环。有没有办法避免错误?制作一个像 finalInstructions 这样的新闭包,就像一个容器一样,似乎也有点笨拙。

这是我没想到的一些有趣的语义,但您看到的是无限递归,由于堆栈溢出而崩溃。您可以通过 运行 项目中的此代码来确认这一点,调试器将在其中捕获错误。您会看到同一闭包的数千个堆栈帧被调用。

您的闭包中对 instructions 的引用似乎正在捕获其新的自引用值,而不是其先前的值。

您使用像 finalInstructions 这样的新变量的方法要好得多。不仅因为它避免了这个问题,还因为它的代码更具可读性。

这是一个更简单、更简单的问题演示:

var closure = { print("initial definition") }

closure() // prints "initial definition"

closure = {
    print("second definition")
    closure()
}

closure() // prints "second definition" repeatedly, until the stack overflows

仅供参考,我在 Swift 论坛上问过这个问题:https://forums.swift.org/t/mutable-closures-can-capture-themselves/43228

把它们放在一起很容易。像 C#'s delegates 那样拆开它们并不容易。

为此,您需要维护一堆如下所示的垃圾。现在最好使用 Combine。

final class MultiClosureTestCase: XCTestCase {
  func test_multiClosure() {
    var x = 0
    let closures: [EquatableClosure<()>] = [
      .init { _ in x += 1 },
      .init { _ in x += 2 }
    ]
    let multiClosure = MultiClosure(closures)
    multiClosure()
    XCTAssertEqual(x, 3)
    multiClosure -= closures.first!
    multiClosure()
    XCTAssertEqual(x, 5)
  }
   
  func test_setRemoval() {
    let closure: EquatableClosure<()>! = .init { _ in }
    let multiClosure = MultiClosure(closure)
    
    XCTAssertNotEqual(multiClosure.closures, [])
    XCTAssertNotEqual(closure.multiClosures, [])
    multiClosure -= closure
    XCTAssertEqual(multiClosure.closures, [])
    XCTAssertEqual(closure.multiClosures, [])
  }

  func test_deallocation() {
    var closure: EquatableClosure<()>! = .init { _ in }
    var multiClosure: MultiClosure! = .init(closure)

    XCTAssertNotEqual(multiClosure.closures, [])
    closure = nil
    XCTAssertEqual(multiClosure.closures, [])

    closure = EquatableClosure { _ in }
    multiClosure += closure
    XCTAssertNotEqual(closure.multiClosures, [])
    multiClosure = nil
    XCTAssertEqual(closure.multiClosures, [])
  }
}
/// Use this until Swift has unowned-referencing collections.
public final class UnownedReferencer<Reference: AnyObject>: HashableViaID {
  public init(_ reference: Reference) {
    self.reference = reference
  }

  public unowned let reference: Reference
}

/// Use this until Swift has weak-referencing collections.
public final class WeakReferencer<Reference: AnyObject>: HashableViaID {
  public init(_ reference: Reference) {
    self.reference = reference
  }

  public weak var reference: Reference?
}

/// Remove the first `UnownedReferencer` with this `reference`
public func -= <Reference: Equatable>(
  set: inout Set< UnownedReferencer<Reference> >,
  reference: Reference
) {
  guard let referencer = ( set.first { [=11=].reference == reference} )
  else { return }

  set.remove(referencer)
}

/// Remove the first `WeakReferencer` with this `reference`
public func -= <Reference: Equatable>(
  set: inout Set< WeakReferencer<Reference> >,
  reference: Reference
) {
  guard let referencer = ( set.first { [=11=].reference == reference } )
  else { return }

  set.remove(referencer)
}
/// A workaround for Swift not providing a way to remove closures
/// from a collection of closures.
///- Note: Designed for one-to-many events, hence no return value.
///  Returning a single value from multiple closures doesn't make sense.
public final class MultiClosure<Input>: Equatable {
  public init(_ closures: EquatableClosure<Input>...) {
    self += closures
  }

  public init<Closures: Sequence>(_ closures: Closures)
  where Closures.Element == EquatableClosure<Input> {
    self += closures
  }

  var closures: Set<
    UnownedReferencer< EquatableClosure<Input> >
  > = []

// MARK: deallocation
  // We can't find self in `closures` without this.
  fileprivate lazy var unownedSelf = UnownedReferencer(self)

  // Even though this MultiClosure will be deallocated,
  // its corresponding WeakReferencers won't be,
  // unless we take this manual action or similar.
  deinit {
    for closure in closures {
      closure.reference.multiClosures.remove(unownedSelf)
    }
  }
}

public extension MultiClosure {
  /// Execute every closure
  func callAsFunction(_ input: Input) {
    for closure in closures {
      closure.reference(input)
    }
  }
}

public extension MultiClosure where Input == () {
  /// Execute every closure
  func callAsFunction() {
    self(())
  }
}

/// A wrapper around a closure, for use with MultiClosures
public final class EquatableClosure<Input>: Equatable {
  public init(_ closure: @escaping (Input) -> Void) {
    self.closure = closure
  }

  /// Execute the closure
  func callAsFunction(_ input: Input) {
    closure(input)
  }

  private let closure: (Input) -> Void

// MARK: deallocation
  var multiClosures: Set<
    UnownedReferencer< MultiClosure<Input> >
  > = []

  // We can't find self in `multiClosures` without this.
  fileprivate lazy var unownedSelf = UnownedReferencer(self)

  deinit {
    for multiClosure in multiClosures {
      multiClosure.reference.closures.remove(unownedSelf)
    }
  }
}

/// Add `closure` to the set of closures that runs
/// when `multiClosure` does
public func += <Input>(
  multiClosure: MultiClosure<Input>,
  closure: EquatableClosure<Input>
) {
  multiClosure.closures.formUnion([closure.unownedSelf])
  closure.multiClosures.formUnion([multiClosure.unownedSelf])
}

/// Add `closures` to the set of closures that runs
/// when `multiClosure` does
public func += <
  Input,
  Closures: Sequence
>(
  multiClosure: MultiClosure<Input>,
  closures: Closures
)
where Closures.Element == EquatableClosure<Input> {
  for closure in closures {
    multiClosure += closure
  }
}

/// Remove `closure` from the set of closures that runs
/// when `multiClosure` does
public func -= <Input>(
  multiClosure: MultiClosure<Input>,
  closure: EquatableClosure<Input>
) {
  multiClosure.closures.remove(closure.unownedSelf)
  closure.multiClosures.remove(multiClosure.unownedSelf)
}
/// An `Identifiable` instance that uses its `id` for equality and hashability.
public protocol HashableViaID: Hashable, Identifiable { }

// MARK: - Equatable
public extension HashableViaID {
  static func == (equatable0: Self, equatable1: Self) -> Bool {
    equatable0.id == equatable1.id
  }
}

// MARK: - Hashable
public extension HashableViaID {
  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }
}
// MARK: - AnyObject
public extension Equatable where Self: AnyObject {
  static func == (class0: Self, class1: Self) -> Bool {
    class0 === class1
  }
}