如果我将一个闭包传递给这样的函数:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
如果我在[weak self]
的捕获列表中将自己声明为someFunctionWithTrailingClosure
而未在weak
anotherFunctionWithTrailingClosure
的捕获列表中再次将其重新声明为self
成为Optional
类型,但它也成为weak
引用?
谢谢!
答案 0 :(得分:13)
不需要[weak self]
中的anotherFunctionWithTrailingClosure
。
你可以凭经验测试这个:
class Experiment {
func someFunctionWithTrailingClosure(closure: () -> ()) {
print("starting someFunctionWithTrailingClosure")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
closure()
print("finishing someFunctionWithTrailingClosure")
}
}
func anotherFunctionWithTrailingClosure(closure: () -> ()) {
print("starting anotherFunctionWithTrailingClosure")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
closure()
print("finishing anotherFunctionWithTrailingClosure")
}
}
func doSomething() {
print("doSomething")
}
func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
然后:
func performExperiment() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let obj = Experiment()
obj.testCompletionHandlers()
// sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler
NSThread.sleepForTimeInterval(1.5)
}
}
如果你这样做,你会发现永远不会调用doSomething
,并且在deinit
调用其关闭之前调用anotherFunctionWithTrailingClosure
。
在验证了这种行为之后,我个人仍然倾向于使用[weak self]
上的anotherFunctionWithTrailingClosure
语法来明确我的意图,因为所有这些都不是很明显。
答案 1 :(得分:2)
已为Swift 4.2更新:
Pacing
尝试Playgorund:
public class CaptureListExperiment {
public init() {
}
func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting someFunctionWithTrailingClosure")
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
closure()
print("finishing someFunctionWithTrailingClosure")
}
}
func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting anotherFunctionWithTrailingClosure")
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
closure()
print("finishing anotherFunctionWithTrailingClosure")
}
}
func doSomething() {
print("doSomething")
}
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { // [weak self] in
self.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
答案 2 :(得分:2)
TL; DR
尽管在 outer 块中一次使用[weak self]
是可以的(EX1),但如果将此引用更改为“ strong”(例如guard let self = self
),则需要一个{ {1}}也位于内部代码块(EX3)。
在 inner 块上也仅使用一次[weak self]
通常是错误(EX2_B)。不幸的是,这是重构代码时常犯的错误,并且在编写时很难发现。
一个好的经验法则是,如果对象在闭包外部紧紧,则始终使用[weak self]
。
不保留weak
的示例(即通常是“好”方案):
self
// EX1
fn { [weak self] in
self?.foo()
}
// EX2
fn { [weak self] in
fn2 {
self?.foo()
}
}
// self is weak inside fn, thus adding an extra `[weak self]` inside fn2 is unnecessary
确实保留// EX3
fn { [weak self] in
guard let self = self else { return }
fn2 { [weak self] in
self?.foo()
}
}
的示例(即通常是“不良”情况):
self
// EX1_B
fn {
self.foo()
}
// fn retains self
// EX2_B
fn {
fn2 { [weak self] in
self.foo()
}
}
// fn retains self (this is a common, hard-to-spot mistake)
与Hamish alludes to一样,// EX3_B
fn { [weak self] in
guard let self = self else { return }
fn2 {
self.foo()
}
}
// fn2 retains self
有用的主要原因有两个:
在Rob's example中,该函数未保留闭包(在dispatch_async之外,但保证将来会在某个时候触发闭包),因此您永远不会以保留周期结束。因此,在这种情况下使用weak
是为了防止#2发生。
如Hamish所提到的,由于没有保留周期,因此在本示例中实际上不需要弱以防止保留周期。在这种情况下,weak
用于防止对象的生存时间超过所需的时间。当您考虑某个对象的寿命超过所需时间时,这完全取决于您的用例。因此,有时您只想在外部(EX2)上使用weak
,而有时又想使用weak
外部,weak
内部,strong
内部舞曲(EX3)。
要检查保留周期问题,假设一个函数正在存储对该块(即堆)的引用,而不是直接引用该函数(即堆栈)。很多时候我们都不知道类/函数的内部,因此更安全地假设函数 保留了该块。
现在,您可以使用外部weak
并仅使用内部weak
(EX3_B)轻松创建保留周期:
strong
请注意,由于创建了保留周期,因此未调用public class CaptureListExperiment {
public init() {
}
var _someFunctionWithTrailingClosure: (() -> ())?
var _anotherFunctionWithTrailingClosure: (() -> ())?
func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting someFunctionWithTrailingClosure")
_someFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._someFunctionWithTrailingClosure!()
print("finishing someFunctionWithTrailingClosure")
}
}
func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting anotherFunctionWithTrailingClosure")
_anotherFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._anotherFunctionWithTrailingClosure!()
print("finishing anotherFunctionWithTrailingClosure")
}
}
func doSomething() {
print("doSomething")
}
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { // [weak self] in
self.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
func performExperiment() {
let obj = CaptureListExperiment()
obj.testCompletionHandlers()
Thread.sleep(forTimeInterval: 1.3)
}
performExperiment()
/* Output:
starting someFunctionWithTrailingClosure
starting anotherFunctionWithTrailingClosure
finishing someFunctionWithTrailingClosure
doSomething
finishing anotherFunctionWithTrailingClosure
*/
。
可以通过删除deinit
引用(EX2)来解决此问题:
strong
或使用弱/强/弱舞蹈(EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
//guard let self = self else { return }
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}