Безопасно ли принудительно изменять переменные разворота, которые были доступны для доступа в одной строке кода?
someFunction(completion: { [weak self] in
self?.variable = self!.otherVariable
})
Безопасен ли этот всегда? Я обращаюсь к необязательному self
в начале инструкции, и лично полагаю, что вторая часть этого оператора никогда не будет выполнена, если self
- nil
. Это правда? Если self
действительно есть nil
, вторая часть никогда не произойдет? И никогда не произойдет, что self
может быть "заполнен" во время этой отдельной строки кода?
Ответы
Ответ 1
Дополнительная цепочка
из "Язык быстрого программирования"
дает следующий пример:
let john = Person()
// ...
let someAddress = Address()
// ...
john.residence?.address = someAddress
за которым следует (выделено мной):
В этом примере попытка установить свойство адреса john.residence не удастся, так как john.residence в настоящее время равно нулю.
Назначение является частью необязательной цепочки, что означает ни один из кода в правой части оператора =.
Применимо к вашему делу: В
self?.variable = self!.otherVariable
правая часть не оценивается, если self
- nil
.
Поэтому ответ на ваш вопрос
Если я действительно равен нулю, вторая часть никогда не произойдет?
- "да". Что касается второго вопроса
И никогда не произойдет, что self может быть "заполнен" во время этой отдельной строки кода?
Я предполагаю, что однажды self
было определено как != nil
,
сильная ссылка на self!
проводится на протяжении всей оценки
так что этого не может произойти. (Теперь подтверждено в
dfri answer.)
Ответ 2
Я опишу этот ответ на мой комментарий на @appzYourLife: s удаленный ответ:
Это чисто спекуляция, но, учитывая несколько близкую связь между многими из опытных разработчиков Swift и С++: s Boost lib, я бы предположил, что ссылка weak
заблокирована сильный для срока жизни выражения, если это присваивает/мутирует что-то в self
, очень похожее на явно используемое std::weak_ptr::lock()
аналога С++.
Посмотрим на ваш пример, где self
был захвачен ссылкой weak
и не является nil
при обращении к левой стороне выражения присваивания
self?.variable = self!.otherVariable
/* ^ ^^^^^-- what about this then?
|
\-- we'll assume this is a success */
Мы можем посмотреть на базовую обработку ссылок weak
(Swift) во время выполнения Swift, swift/include/swift/Runtime/HeapObject.h
:
/// Load a value from a weak reference. If the current value is a
/// non-null object that has begun deallocation, returns null;
/// otherwise, retains the object before returning.
///
/// \param ref - never null
/// \return can be null
SWIFT_RUNTIME_EXPORT
HeapObject *swift_weakLoadStrong(WeakReference *ref);
Ключ здесь - комментарий
Если текущее значение является ненулевым объектом, который начал освобождение, возвращает null; в противном случае сохраняет объект перед возвратом.
Так как это основано на комментарии кода backend runtime, это все еще несколько умозрительно, но я бы сказал, что из вышеизложенного следует, что при попытке получить доступ к значению, указанному ссылкой weak
, ссылка действительно будет сохранена как сильный для жизни вызова ( "... до возвращения" ).
Чтобы попытаться выкупить "несколько спекулятивную" часть сверху, мы можем продолжать понимать, как Swift обрабатывает доступ к значению с помощью ссылки weak
. Из @idmean: комментарий ниже (изучение сгенерированного кода SIL для примера, такого как OP: s), мы знаем, что вызывается функция swift_weakLoadStrong(...)
.
Итак, мы начнем с изучения функции swift_weakLoadStrong(...)
в swift/stdlib/public/runtime/HeapObject.cpp
и посмотрим, откуда мы получим есть:
HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
return ref->nativeLoadStrong();
}
Мы находим реализацию метода nativeLoadStrong()
WeakReference
из swift/include/swift/Runtime/HeapObject.h
HeapObject *nativeLoadStrong() {
auto bits = nativeValue.load(std::memory_order_relaxed);
return nativeLoadStrongFromBits(bits);
}
Из тот же файл, реализация nativeLoadStrongFromBits(...)
:
HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
return side ? side->tryRetain() : nullptr;
}
Продолжая цепочку вызовов, tryRetain()
является методом HeapObjectSideTableEntry
(что существенно для машины состояния жизненного цикла объекта), и мы находим его реализацию в swift/stdlib/public/SwiftShims/RefCount.h
HeapObject* tryRetain() {
if (refCounts.tryIncrement())
return object.load(std::memory_order_relaxed);
else
return nullptr;
}
Реализация метода tryIncrement()
типа RefCounts
(здесь вызывается через экземпляр typedef
: ed специализация этого) можно найти в том же файле, что и выше:
// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
...
}
Я считаю, что здесь нам достаточно комментариев, чтобы использовать этот метод в качестве конечной точки: если объект не деинизирует (который мы предположили выше, что это не так, как lhs
присвоения в OP: s пример считается успешным), (сильный) счетчик ссылок на объект будет увеличен, а указатель HeapObject
(подкрепленный сильным увеличением счетчика ссылок) будет передан оператору присваивания. Нам не нужно изучать, как в конечном итоге будет выполняться соответствующее сокращение счетчика ссылок, но теперь известно, что объект, связанный с ссылкой weak
, будет сохранен как сильный для времени жизни присваивания, учитывая, что он не был освобожден/освобожден во время доступа к нему левой руки (в этом случае его правая часть никогда не будет обработана, как было объяснено в @MartinR: s ответ).
Ответ 3
Это всегда безопасно
Нет. Вы не занимаетесь "слабым сильным танцем". Сделай это! Всякий раз, когда вы используете weak self
, вы должны безопасно разворачивать опцию, а затем ссылаться только на результат этой разворачивания - например:
someFunction(completion: { [weak self] in
if let sself = self { // safe unwrap
// now refer only to `sself` here
sself.variable = sself.otherVariable
// ... and so on ...
}
})
Ответ 4
В документации явно указывается, что если левая сторона задания определяется как нуль, правая сторона не будет оцениваться.
Однако в данном примере self
есть слабая ссылка и может быть выпущена (и аннулирована) сразу после прохождения дополнительной проверки, но как раз перед тем, как произойдет разворот силы, сделав все выражение nil-unsafe.
Ответ 5
ПЕРЕД КОРРЕКЦИЕЙ:
Я думаю, что другие ответили на детали вашего вопроса гораздо лучше, чем могли.
Но помимо обучения. Если вы действительно хотите, чтобы ваш код надежно работал, тогда лучше всего сделать следующее:
someFunction(completion: { [weak self] in
guard let _ = self else{
print("self was nil. End of discussion")
return
}
print("we now have safely 'captured' a self, no need to worry about this issue")
self?.variable = self!.otherVariable
self!.someOthervariable = self!.otherVariable
}
ПОСЛЕ КОРРЕКЦИИ.
Благодаря объяснениям MartinR ниже я многому научился.
Чтение из этого замечательного сообщения при закрытии захвата. Я рабски думал, когда вы видите что-то в скобках []
, это означает, что оно захвачено и его значение не меняется. Но единственное, что мы делаем в скобках, это то, что мы weak
-описываем его и сообщаем себе, что его значение может стать nil
. Если бы мы сделали что-то вроде [x = self]
, мы бы успешно его захватили, но тогда у нас была бы проблема с крепким указателем на self
и создать цикл памяти. (Интересно в смысле, что это очень тонкая линия от перехода к созданию цикла памяти для создания сбоя из-за того, что значение освобождается, потому что вы его слабый).
Итак, заключаем:
-
[capturedSelf = self]
(создает цикл памяти)
-
[weak self] in guard let _ = self else {return}
(может привести к сбою, если вы принудительно развернете self
впоследствии)
-
[weak self] in guard let strongSelf = self else { return}
(безопасно, если self
был освобожден или продолжен, если не nil
)
В зависимости от ваших потребностей вы должны сделать что-то между вариантом 2 или 3