Подтвердить что ты не робот

Безопасно ли принудительно изменять переменные разворота, которые были доступны для доступа в одной строке кода?

someFunction(completion: { [weak self] in
    self?.variable = self!.otherVariable
})

Безопасен ли этот всегда? Я обращаюсь к необязательному self в начале инструкции, и лично полагаю, что вторая часть этого оператора никогда не будет выполнена, если self - nil. Это правда? Если self действительно есть nil, вторая часть никогда не произойдет? И никогда не произойдет, что self может быть "заполнен" во время этой отдельной строки кода?

4b9b3361

Ответ 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