如果我有一个闭包传递给这样的函数:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
如果我在someFunctionWithTrailingClosure
的捕获列表中将自己声明为[weak self]
,而在anotherFunctionWithTrailingClosure
的捕获列表中不将其重新声明为weak
,那么self
已经成为Optional
类型,但它是否也成为weak
引用?
谢谢!
不需要anotherFunctionWithTrailingClosure
中的[weak self]
。
你可以凭经验测试一下:
class Experiment {
func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}
func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}
func doSomething() {
print(#function)
}
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() {
DispatchQueue.global().async {
let obj = Experiment()
obj.testCompletionHandlers()
// sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler
Thread.sleep(forTimeInterval: 1.5)
}
}
如果您这样做,您将看到doSomething
从未被调用,并且deinit
在anotherFunctionWithTrailingClosure
调用其闭包之前被调用。
话虽如此,我可能仍然倾向于在anotherFunctionWithTrailingClosure
上使用[weak self]
语法来明确我的意图。
TL;博士
尽管在外部块中使用[weak self]
一次是可以的(EX1),但如果将此引用更改为强(例如guard let self = self
),则在内部块中也需要[weak self]
(EX3)。
此外,在内部块上仅使用[weak self]
一次通常是错误(EX2_B)。不幸的是,在重构代码时,这是一个常见的错误,而且在进行重构时很难发现。
一个好的经验法则是,如果对象在闭包外很强,则始终使用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)
// EX3_B
fn { [weak self] in
guard let self = self else { return }
fn2 {
self.foo()
}
}
// fn2 retains self
正如Hamish所暗示的,weak
有用的主要原因有两个:
- 以防止保留循环
- 防止物体寿命超过应有时间
关于#2的更多信息(防止长寿命物体)
在Rob的例子中,函数不是保留闭包(除了dispatch_async之外,它几乎可以保证在未来的某个时候触发闭包),因此永远不会出现保留循环。因此,在这种情况下使用weak
是为了防止#2的发生。
正如Hamish所提到的,在这个例子中,实际上不需要弱来防止保留循环,因为没有保留循环。在这种情况下,weak
用于防止对象寿命超过所需时间。这完全取决于你的用例,当你考虑一个物体的寿命超过需要的时候。因此,例如,有时您希望仅在外部使用weak
(EX2),而有时您希望使用weak
外部、strong
内部、weak
内部舞蹈(EX3)。
关于#1的更多信息(防止保留循环)
为了研究保留周期问题,假设一个函数存储对块(即堆)的引用,而不是直接引用函数(即堆栈)。很多时候,我们不知道类/函数的内部,所以假设函数是保留块更安全。
现在,您可以使用weak
外部和仅使用strong
内部(EX3_B)来轻松创建保留循环:
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
。
这可以通过移除strong
参考(EX2):来解决
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
//guard let self = self else { return }
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}
或者使用弱/强/弱舞蹈(EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
}
为Swift 4.2更新:
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")
}
}
试试看Playgorund:
func performExperiment() {
let obj = CaptureListExperiment()
obj.testCompletionHandlers()
Thread.sleep(forTimeInterval: 1.3)
}
performExperiment()