Нужно ли перехватывать списки внутренних замыканий для обозначения себя как слабого или неизвестного?
Если у меня есть замыкание передано в функцию, как это:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
Если я объявлю себя как [weak self]
в someFunctionWithTrailingClosure
список перехвата без повторного выделения его как weak
снова в списке захвата anotherFunctionWithTrailingClosure
self
уже становится Optional
типа, но это также становится weak
Ссылка, а?
Спасибо!
2 ответа
[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
сделать мое намерение явным, поскольку все это далеко не очевидно.
TL;DR
Хотя использование [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
Как намекает Хэмиш, есть две основные причиныweak
Полезно:
- Чтобы предотвратить циклы сохранения.
- Чтобы предметы не жили дольше, чем они должны быть.
Подробнее о #2 (предотвращение долгоживущих объектов)
В примере Роба функция не сохраняет закрытие (помимо dispatch_async, который почти гарантированно запустит закрытие в какой-то момент в будущем), поэтому у вас никогда не будет цикла сохранения. Итак, используяweak
в этом случае, значит, предотвратить # 2.
Как упоминает Хэмиш, в этом примере weak на самом деле не требуется для предотвращения циклов сохранения, поскольку циклов сохранения нет. 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()
}
}
}
Или используя танец слабый / сильный / слабый (Пример 3):
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()