example basics arc apple ios swift memory-management null optional

ios - basics - swift apple example



¿Es seguro forzar desenvolver variables a las que se ha accedido opcionalmente en la misma línea de código? (5)

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

¿Esto siempre es seguro? Accedo al self opcional al principio de la declaración, y personalmente asumo que la segunda parte de esta declaración nunca se ejecutará si self soy nil . ¿Es esto cierto? Si el self verdad es nil , la segunda parte nunca sucederá? ¿Y nunca sucederá que el self pueda ser ''anulado'' durante esta única línea de código?


Esto es siempre seguro

No No estás haciendo el "baile débil y fuerte". ¡Hazlo! Siempre que use el weak self , debe desenvolver el Opcional de forma segura, y luego referirse solo al resultado de ese desenvolvimiento, como este:

someFunction(completion: { [weak self] in if let sself = self { // safe unwrap // now refer only to `sself` here sself.variable = sself.otherVariable // ... and so on ... } })


Basaré esta respuesta en mi comentario a @appzYourLife: s respuesta eliminada:

Esto es pura especulación, pero considerando la conexión algo cercana entre muchos de los experimentados desarrolladores de núcleos Swift y C ++: s Boost lib, asumiría que weak referencia weak está bloqueada en una fuerte durante la vida de la expresión, si esto asigna / muta algo en self , al igual que el std::weak_ptr::lock() explícitamente utilizado de la contraparte de C ++.

Echemos un vistazo a su ejemplo, donde el self ha sido capturado por una referencia weak y no es nil al acceder al lado izquierdo de la expresión de asignación

self?.variable = self!.otherVariable /* ^ ^^^^^-- what about this then? | /-- we''ll assume this is a success */

Podemos ver el tratamiento subyacente de weak referencias weak (Swift) en el tiempo de ejecución de Swift, swift/include/swift/Runtime/HeapObject.h específicamente :

/// 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);

La clave aquí es el comentario

Si el valor actual es un objeto no nulo que ha comenzado la desasignación, devuelve nulo; de lo contrario, conserva el objeto antes de regresar .

Dado que esto se basa en el comentario del código de tiempo de ejecución, todavía es algo especulativo, pero diría que lo anterior implica que cuando se intenta acceder al valor apuntado por una referencia weak , la referencia se mantendrá como una fuerte para toda la vida de la llamada ( "... hasta que regrese" ).

Para tratar de canjear la parte "algo especulativa" de arriba, podemos seguir profundizando en cómo Swift maneja el acceso de un valor a través de una referencia weak . Del comentario de @idmean: s a continuación (estudiando el código SIL generado para un ejemplo como el OP: s) sabemos que se llama a la función swift_weakLoadStrong(...) .

Así que comenzaremos investigando la implementación de la función swift_weakLoadStrong(...) en swift/stdlib/public/runtime/HeapObject.cpp y veremos de dónde swift/stdlib/public/runtime/HeapObject.cpp :

HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) { return ref->nativeLoadStrong(); }

Encontramos la implementación del método nativeLoadStrong() de WeakReference de swift/include/swift/Runtime/HeapObject.h

HeapObject *nativeLoadStrong() { auto bits = nativeValue.load(std::memory_order_relaxed); return nativeLoadStrongFromBits(bits); }

Desde el mismo archivo , la implementación de nativeLoadStrongFromBits(...) :

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) { auto side = bits.getNativeOrNull(); return side ? side->tryRetain() : nullptr; }

Continuando a lo largo de la cadena de llamadas, tryRetain() es un método de HeapObjectSideTableEntry (que es esencial para la máquina de estado del ciclo de vida del objeto ), y encontramos su implementación en swift/stdlib/public/SwiftShims/RefCount.h

HeapObject* tryRetain() { if (refCounts.tryIncrement()) return object.load(std::memory_order_relaxed); else return nullptr; }

La implementación del método tryIncrement() del tipo RefCounts (aquí invocado mediante una instancia de una especialización typedef : ed ) se puede encontrar en el mismo archivo que el anterior :

// Increment the reference count, unless the object is deiniting. bool tryIncrement() { ... }

Creo que el comentario aquí es suficiente para que usemos este método como punto final: si el objeto no está deinición (lo que hemos asumido anteriormente, no es así, ya que se supone que el lhs de la asignación en el ejemplo del OP es satisfactorio), se aumentará el recuento de referencias (fuertes) en el objeto y se HeapObject un puntero HeapObject (respaldado por un fuerte incremento de recuento de referencias) al operador de asignación. No es necesario estudiar cómo se realiza finalmente el decremento de recuento de referencia correspondiente al final de la asignación, pero ahora sabemos más allá de la especulación de que el objeto asociado a la referencia weak se mantendrá como uno fuerte durante la vida de la asignación, dado que no se ha liberado / desasignado en el momento del acceso desde el lado izquierdo de la misma (en cuyo caso el lado derecho de la misma nunca se procesará, como se explicó en la respuesta de @MartinR ).


La documentación establece claramente que, si se determina que el lado izquierdo de la asignación es nulo, no se evaluará el lado derecho. Sin embargo, en el ejemplo dado, self es una referencia débil y puede liberarse (y anularse) justo después de que pase la verificación opcional, pero justo antes de que se produzca el desenredo forzado, haciendo que la expresión entera no sea peligrosa.


El encadenamiento opcional de "El lenguaje de programación Swift" ofrece el siguiente ejemplo:

let john = Person() // ... let someAddress = Address() // ... john.residence?.address = someAddress

seguido de (énfasis agregado):

En este ejemplo, el intento de establecer la propiedad de dirección de john.residence fracasará, porque john.residence es actualmente nula.

La asignación es parte del encadenamiento opcional, lo que significa que no se evalúa ninguno de los códigos en el lado derecho del operador =.

Aplicado a su caso: En

self?.variable = self!.otherVariable

el lado derecho no se evalúa si el self es nil . Por lo tanto, la respuesta a tu pregunta

Si el ser en verdad es nulo, la segunda parte nunca sucederá?

Es sí". Con respecto a la segunda pregunta

¿Y nunca sucederá que el yo pueda ser ''anulado'' durante esta única línea de código?

¡ Supongo que una vez que self ha determinado que yo soy != nil , ¡una fuerte referencia al self! se lleva a cabo durante la evaluación de la declaración, para que esto no pueda suceder. (Ahora confirmado en la respuesta de dfri )


ANTES DE LA CORRECCIÓN:

Creo que otros han respondido los detalles de su pregunta mucho mejor que yo.

Pero aparte de aprender. Si realmente quiere que su código funcione de manera confiable, entonces es mejor hacerlo así:

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 }

DESPUÉS DE CORREGIR.

Gracias a la explicación de MartinR a continuación, aprendí mucho.

Leyendo de este gran post sobre captura de cierre . Creo servilmente que cuando ves algo entre corchetes [] significa que se captura y su valor no cambia. Pero lo único que estamos haciendo entre corchetes es que estamos debilitándonos y dándonos a conocer a nosotros mismos que su valor podría ser nil . Si hubiésemos hecho algo como [x = self] , lo habríamos capturado con éxito, pero aun así tendríamos el problema de mantener un puntero fuerte al self mismo y crear un ciclo de memoria. (Es interesante en el sentido de que es una línea muy delgada, desde crear un ciclo de memoria hasta crear un bloqueo debido al valor desasignado porque lo debilitó).

Entonces para concluir:

  1. [capturedSelf = self]

    crea un ciclo de memoria. ¡No está bien!

  2. [weak self] in guard let _ = self else{ return }

    (puede provocar un choque si fuerza la self desenvuelta). El guard let completamente inútil. Debido a que la siguiente línea, self puedo volverme nil . ¡No está bien!

  3. [weak self] self?.method1()

    (Puede provocar un colapso si fuerza self desenrollamiento self después. Pasaría si el self no es nil . Fallaría de manera segura si el self es nil .) Esto es lo más probable que desee. Esto es bueno !

  4. [weak self] in guard let strongSelf = self else{ return }

    Fallará de manera segura si el self fue desasignado o si no procede procederá. Pero es un poco contrario al propósito, porque no debería necesitar comunicarse consigo self cuando eliminó su propia referencia. No puedo pensar en un buen caso de uso para esto. ¡Esto es probablemente inútil!