如何将闭包附加到另一个闭包?
How to append a closure to another closure?
我的第一个闭包在这里:
var instructions: (() -> Void) = {
print("First")
}
instructions() /// prints "First"
现在我有另一个闭包:
let additionalInstructions: (() -> Void) = {
print("Second")
}
additionalInstructions() /// prints "Second"
我想 “追加” additionalInstructions
到 instructions
的末尾...这可能吗?我尝试制作一个包含它们的新闭包,如下所示:
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
}
}
我的第一个闭包在这里:
var instructions: (() -> Void) = {
print("First")
}
instructions() /// prints "First"
现在我有另一个闭包:
let additionalInstructions: (() -> Void) = {
print("Second")
}
additionalInstructions() /// prints "Second"
我想 “追加” additionalInstructions
到 instructions
的末尾...这可能吗?我尝试制作一个包含它们的新闭包,如下所示:
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
}
}