¿Por qué crear "Opcionales implícitamente sin envolver", ya que eso implica que sabes que hay un valor?

493

¿Por qué crearía un "Opcional implícitamente sin envolver" en lugar de crear solo una variable o constante regular? Si sabe que se puede desenvolver con éxito, ¿por qué crear un opcional en primer lugar? Por ejemplo, por qué es esto:

let someString: String! = "this is the string"

va a ser más útil que:

let someString: String = "this is the string"

Si "opcionales indican que se permite que una constante o variable tenga" ningún valor "", pero "a veces queda claro a partir de la estructura de un programa que un opcional siempre tendrá un valor después de que ese valor se establezca por primera vez", ¿cuál es el punto de convirtiéndolo en opcional en primer lugar? Si sabe que un opcional siempre tendrá un valor, ¿no lo hace no opcional?

Johnston
fuente

Respuestas:

127

Considere el caso de un objeto que puede tener propiedades nulas mientras se está construyendo y configurando, pero es inmutable y no nulo después (NSImage a menudo se trata de esta manera, aunque en su caso todavía es útil mutar a veces). Las opciones implícitamente desenvueltas limpiarían su código en gran medida, con una pérdida de seguridad relativamente baja (siempre y cuando la única garantía se mantenga, sería segura).

(Editar) Sin embargo, para que quede claro: las opciones regulares son casi siempre preferibles.

Catfish_Man
fuente
459

Antes de que pueda describir los casos de uso de los opcionales implícitamente sin envolver, ya debe comprender qué son los opcionales y los opcionales implícitamente sin envolver en Swift. Si no lo hace, le recomiendo que primero lea mi artículo sobre opciones

Cuándo usar una opción opcional sin envolver implícitamente

Hay dos razones principales por las que uno crearía un Opcional implícitamente sin envolver. Todo tiene que ver con la definición de una variable a la que nunca se accederá nilporque, de lo contrario, el compilador Swift siempre lo obligará a desenvolver explícitamente un Opcional.

1. Una constante que no se puede definir durante la inicialización

Cada constante de miembro debe tener un valor para cuando se complete la inicialización. A veces, una constante no se puede inicializar con su valor correcto durante la inicialización, pero aún se puede garantizar que tenga un valor antes de acceder.

El uso de una variable Opcional soluciona este problema porque un Opcional se inicializa automáticamente nily el valor que eventualmente contendrá seguirá siendo inmutable. Sin embargo, puede ser una molestia desenvolver constantemente una variable que usted sabe con certeza que no es nula. Los opcionales sin envoltura implícitamente logran los mismos beneficios que un opcional con el beneficio adicional de que uno no tiene que desenvolverlo explícitamente en todas partes.

Un gran ejemplo de esto es cuando una variable miembro no se puede inicializar en una subclase de UIView hasta que se carga la vista:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

Aquí, no puede calcular el ancho original del botón hasta que se cargue la vista, pero sabe que awakeFromNib se llamará antes que cualquier otro método en la vista (que no sea la inicialización). En lugar de forzar que el valor se desenvuelva explícitamente sin sentido en toda su clase, puede declararlo como Opcionalmente sin envolver opcional.

2. Cuando su aplicación no puede recuperarse de un ser variable nil

Esto debería ser extremadamente raro, pero si su aplicación no puede continuar ejecutándose si se nilaccede a una variable , sería una pérdida de tiempo molestarse en probarla nil. Normalmente, si tiene una condición que debe ser absolutamente cierta para que su aplicación continúe ejecutándose, usaría un assert. Un Opcional implícitamente sin envolver tiene una aserción para nil integrada directamente en él. Incluso entonces, a menudo es bueno desenvolver lo opcional y usar una afirmación más descriptiva si es nula.

Cuándo no utilizar un opcional implícitamente desenvuelto

1. Variables de miembro calculadas perezosamente

A veces tiene una variable miembro que nunca debe ser nula, pero no se puede establecer en el valor correcto durante la inicialización. Una solución es usar un Opcional implícitamente sin envolver, pero una mejor manera es usar una variable perezosa:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

Ahora, la variable miembro contentsno se inicializa hasta la primera vez que se accede a ella. Esto le da a la clase la oportunidad de llegar al estado correcto antes de calcular el valor inicial.

Nota: Esto puede parecer contradecir el n. ° 1 desde arriba. Sin embargo, hay una distinción importante que hacer. Lo buttonOriginalWidthanterior debe establecerse durante viewDidLoad para evitar que alguien cambie el ancho de los botones antes de acceder a la propiedad.

2. En todas partes

En su mayor parte, se deben evitar las opciones implícitas sin envolver porque si se usa por error, toda la aplicación se bloqueará cuando se acceda a ella nil. Si alguna vez no está seguro de si una variable puede ser nula, use siempre un Opcional normal de forma predeterminada. Desenvolver una variable que nunca es segura no nilhace mucho daño.

Drewag
fuente
44
Esta respuesta debe actualizarse para la versión beta 5. Ya no puede usarla if someOptional.
Santa Claus
2
@SantaClaus hasValuese define directamente en Opcional. Prefiero la semántica hasValuea la de != nil. Siento que es mucho más comprensible para los nuevos programadores que no han usado nilen otros idiomas. hasValueEs mucho más lógico que nil.
drewag
2
Parece que hasValuefue sacado de la versión beta 6. Sin embargo, Ash lo devolvió ... github.com/AshFurrow/hasValue
Chris Wagner
1
@newacct Con respecto al tipo de retorno de los inicializadores Objc, es más una opción implícita implícitamente desenvuelta. El comportamiento que describió para usar un "no opcional" es exactamente lo que haría un Opcionalmente desenvuelto opcionalmente (no fallará hasta que se acceda a él). Con respecto a dejar que el programa falle antes al desenvolverlo por la fuerza, estoy de acuerdo en que se prefiere usar un sistema no opcional, pero esto no siempre es posible.
drewag
1
@confile no. Pase lo que pase, aparecerá en Objective-C como un puntero (si fuera opcional, implícitamente desenvuelto o no opcional).
drewag
56

Los opcionales implícitamente sin envolver son útiles para presentar una propiedad como no opcional cuando realmente necesita ser opcional bajo las cubiertas. Esto es a menudo necesario para "atar el nudo" entre dos objetos relacionados, cada uno de los cuales necesita una referencia al otro. Tiene sentido cuando ninguna de las referencias es realmente opcional, pero una de ellas debe ser nula mientras se inicializa el par.

Por ejemplo:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Cualquier Binstancia siempre debe tener una myBuddyAreferencia válida , por lo que no queremos que el usuario la trate como opcional, pero necesitamos que sea opcional para poder construir una Bantes de que tengamos una Areferencia.

¡SIN EMBARGO! Este tipo de requisito de referencia mutua es a menudo una indicación de acoplamiento apretado y diseño deficiente. Si se encuentra confiando en opciones implícitamente desenvueltas, probablemente debería considerar la refactorización para eliminar las dependencias cruzadas.

n8gray
fuente
77
Creo que una de las razones por las que crearon esta característica del lenguaje es@IBOutlet
Jiaaro
11
+1 para la advertencia "SIN EMBARGO". Puede que no sea cierto todo el tiempo, pero ciertamente es algo a tener en cuenta.
JMD
44
Todavía tiene un ciclo de referencia fuerte entre A y B. Las opciones implícitamente desenvueltas NO crean una referencia débil. Todavía necesita declarar myByddyA o myBuddyB como débiles (probablemente myBuddyA)
drewag
66
Para ser aún más claro por qué esta respuesta es incorrecta y peligrosamente engañosa: los opcionales implícitamente sin envolver no tienen absolutamente nada que ver con la gestión de la memoria y no impiden los ciclos de retención. Sin embargo, los opcionales implícitamente sin envolver siguen siendo útiles en las circunstancias descritas para configurar la referencia bidireccional. Así que simplemente agregue la weakdeclaración y elimine "sin crear un ciclo de retención fuerte"
2014
1
@drewag: Tienes razón: he editado la respuesta para eliminar el ciclo de retención. Tenía la intención de debilitar la referencia, pero supongo que se me olvidó.
n8gray
37

Las opciones implícitamente desenvueltas son un compromiso pragmático para hacer que el trabajo en un entorno híbrido que tiene que interoperar con los marcos de Cocoa existentes y sus convenciones sea más agradable, al tiempo que permite la migración gradual hacia un paradigma de programación más seguro, sin punteros nulos, implementado por el compilador Swift.

El libro de Swift, en el capítulo Lo básico , la sección Opciones implícitas sin envolver dice:

Los opcionales implícitamente desenvueltos son útiles cuando se confirma que el valor de un opcional existe inmediatamente después de que el opcional se define por primera vez y definitivamente se puede suponer que existe en cada punto posterior. El uso principal de las opciones implícitas sin envolver en Swift es durante la inicialización de la clase, como se describe en Referencias sin propietario y Propiedades opcionales implícitamente sin envolver .
...
Puede pensar en un opcional sin envolver implícitamente como un permiso para que el opcional se desenvuelva automáticamente cada vez que se usa. En lugar de colocar un signo de exclamación después del nombre del opcional cada vez que lo usa, coloca un signo de exclamación después del tipo del opcional cuando lo declara.

Esto se reduce a los casos de uso donde la no- nilintegridad de las propiedades se establece a través de la convención de uso, y el compilador no puede imponerla durante la inicialización de la clase. Por ejemplo, las UIViewControllerpropiedades que se inicializan a partir de NIB o Storyboards, donde la inicialización se divide en fases separadas, pero luego viewDidLoad()puede suponer que las propiedades generalmente existen. De lo contrario, para satisfacer al compilador, tenía que usar el desenvolvimiento forzado , el enlace opcional o el encadenamiento opcional solo para ocultar el propósito principal del código.

La parte anterior del libro Swift se refiere también al capítulo Conteo automático de referencias :

Sin embargo, hay un tercer escenario, en el que ambas propiedades siempre deben tener un valor, y ninguna de las dos propiedades debe estar niluna vez que se complete la inicialización. En este escenario, es útil combinar una propiedad no propietaria en una clase con una propiedad opcional implícitamente desenvuelta en la otra clase.

Esto permite acceder a ambas propiedades directamente (sin desempaquetado opcional) una vez que se completa la inicialización, mientras se evita un ciclo de referencia.

Esto se reduce a las peculiaridades de no ser un lenguaje recolectado de basura, por lo tanto, la ruptura de los ciclos de retención depende de usted como programador y las opciones implícitamente desenvueltas son una herramienta para ocultar esta peculiaridad.

Eso cubre el "¿Cuándo usar opciones opcionales implícitamente sin envolver en su código?" pregunta. Como desarrollador de aplicaciones, los encontrará principalmente en firmas de método de bibliotecas escritas en Objective-C, que no tiene la capacidad de expresar tipos opcionales.

De Usar Swift con Cocoa y Objective-C, sección Trabajar con nil :

Dado que Objective-C no garantiza que un objeto sea no nulo, Swift hace que todas las clases en los tipos de argumento y los tipos de retorno sean opcionales en las API de Objective-C importadas. Antes de usar un objeto Objective-C, debe verificar para asegurarse de que no falte.

En algunos casos, puede estar absolutamente seguro de que un método o propiedad Objective-C nunca devuelve una nilreferencia de objeto. Para hacer que los objetos en este escenario especial sean más convenientes para trabajar, Swift importa los tipos de objetos como opciones implícitamente . Los tipos opcionales sin envoltura implícita incluyen todas las características de seguridad de los tipos opcionales. Además, puede acceder al valor directamente sin verificarnilo desenvolverlo usted mismo. Cuando accede al valor en este tipo de tipo opcional sin desenvolverlo con seguridad primero, el opcional opcional sin envolver verifica si falta el valor. Si falta el valor, se produce un error de tiempo de ejecución. Como resultado, siempre debe verificar y desenvolver una opción implícitamente desenvuelta, a menos que esté seguro de que el valor no puede faltar.

... y más allá yacía dragones

Palimondo
fuente
Gracias por esta respuesta completa. ¿Puede pensar en una lista de verificación rápida de cuándo usar un Opcional implícitamente sin envolver y cuándo sería suficiente una variable estándar?
Hairgami_Master
@Hairgami_Master Agregué mi propia respuesta con una lista y ejemplos concretos
2014
18

Los ejemplos simples de una línea (o varias líneas) no cubren muy bien el comportamiento de los opcionales; sí, si declara una variable y le proporciona un valor de inmediato, no tiene sentido en un opcional.

El mejor caso que he visto hasta ahora es la configuración que ocurre después de la inicialización del objeto, seguida del uso que está "garantizado" para seguir esa configuración, por ejemplo, en un controlador de vista:

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

Sabemos printSizeque se llamará después de cargar la vista: es un método de acción conectado a un control dentro de esa vista, y nos aseguramos de no llamarlo de otra manera. Por lo tanto, podemos ahorrarnos algunas comprobaciones / enlaces opcionales con el !. Swift no puede reconocer esa garantía (al menos hasta que Apple resuelva el problema de detención), por lo que le dice al compilador que existe.

Sin embargo, esto rompe la seguridad del tipo hasta cierto punto. Cualquier lugar donde tenga una opción implícitamente desenvuelta es un lugar en el que su aplicación puede bloquearse si su "garantía" no siempre se cumple, por lo que es una característica que debe usar con moderación. Además, usar !todo el tiempo suena como si estuvieras gritando, y a nadie le gusta eso.

rickster
fuente
¿Por qué no simplemente inicializar screenSize a CGSize (alto: 0, ancho: 0) y evitar la molestia de tener que gritar la variable cada vez que accede a ella?
Martin Gordon
Un tamaño podría no haber sido el mejor ejemplo, ya que CGSizeZeropuede ser un buen valor centinela en el uso en el mundo real. Pero, ¿qué pasa si tiene un tamaño cargado desde la punta que en realidad podría ser cero? Luego, usarlo CGSizeZerocomo centinela no te ayuda a distinguir entre un valor que no se establece y se establece en cero. Por otra parte, esto se aplica igualmente bien a otros tipos cargados desde punta (o en cualquier otro lugar después init): las cadenas, las referencias a subvistas, etc.
Rickster
2
Parte del objetivo de Opcional en lenguajes funcionales es no tener valores centinela. O tienes un valor o no. No debería tener un caso en el que tenga un valor que indique que falta un valor.
Wayne Tanner
44
Creo que has entendido mal la pregunta del OP. El OP no está preguntando sobre el caso general de los opcionales, más bien, específicamente sobre la necesidad / uso de los opcionales implícitamente sin envolver (es decir let foo? = 42, no , más bien let foo! = 42). Esto no aborda eso. (Esta puede ser una respuesta relevante sobre los opcionales, claro, pero no sobre los opcionales implícitamente sin envolver que son un animal diferente / relacionado.)
JMD
15

Apple da un gran ejemplo en El lenguaje de programación rápido -> Conteo automático de referencias -> Resolución de ciclos de referencia fuertes entre instancias de clase -> Referencias no deseadas y propiedades opcionales implícitamente desenvueltas

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

El inicializador para Cityse llama desde dentro del inicializador para Country. Sin embargo, el inicializador para Countryno puede pasar selfal Cityinicializador hasta que una nueva Countryinstancia esté completamente inicializada, como se describe en Inicialización de dos fases .

Para hacer frente a este requisito, declara la capitalCitypropiedad de Countrycomo una propiedad opcional implícitamente sin envolver.

Fujianjin6471
fuente
El tutorial mencionado en esta respuesta está aquí .
Franklin Yu
3

La justificación de los opcionales implícitos es más fácil de explicar al observar primero la justificación del desenvolvimiento forzado.

Desenvoltura forzada de un opcional (implícito o no), utilizando el! operador, significa que está seguro de que su código no tiene errores y que el opcional ya tiene un valor donde se está desenvolviendo. Sin el ! operador, probablemente solo afirmaría con un enlace opcional:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

que no es tan bueno como

println("\(value!)")

Ahora, las opciones implícitas le permiten expresar que tiene una opción que espera que siempre tenga un valor cuando se desenvuelve, en todos los flujos posibles. Por lo tanto, va un paso más allá para ayudarlo, ¡al relajar el requisito de escribir el! para desenvolver cada vez, y asegurarse de que el tiempo de ejecución seguirá siendo un error en caso de que sus suposiciones sobre el flujo sean incorrectas.

Danra
fuente
1
@newacct: no nulo en todos los flujos posibles a través de su código no es lo mismo que no nulo desde la inicialización principal (clase / estructura) hasta la desasignación. Interface Builder es el ejemplo clásico (pero hay muchos otros patrones de inicialización retrasada): si una clase solo se usa desde una punta, entonces las variables de salida no se establecerán init(lo que quizás ni siquiera implemente), pero ' re garantizado para ser configurado después de awakeFromNib/ viewDidLoad.
rickster
3

Si lo sabe con certeza, un valor devuelto por un opcional en lugar de uno opcional nil, implícitamente desenvuelto, utiliza para capturar directamente esos valores de los opcionales y los no opcionales no pueden.

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

Entonces esta es la diferencia entre el uso de

let someString : String! y let someString : String

enadun
fuente
1
Esto no responde la pregunta del OP. OP sabe lo que es un Opcional implícitamente desenvuelto.
Franklin Yu
0

Creo que Optionales un mal nombre para esta construcción que confunde a muchos principiantes.

Otros lenguajes (Kotlin y C #, por ejemplo) usan el término Nullable, y hace que sea mucho más fácil de entender esto.

Nullablesignifica que puede asignar un valor nulo a una variable de este tipo. Entonces, si es así Nullable<SomeClassType>, puede asignarle valores nulos, si es solo SomeClassType, no puede. Así es como funciona Swift.

¿Por qué usarlos? Bueno, a veces necesitas tener valores nulos, por eso. Por ejemplo, cuando sabe que desea tener un campo en una clase, pero no puede asignarlo a nada cuando está creando una instancia de esa clase, pero lo hará más adelante. No daré ejemplos, porque la gente ya los ha proporcionado aquí. Solo estoy escribiendo esto para dar mis 2 centavos.

Por cierto, le sugiero que mire cómo funciona esto en otros idiomas, como Kotlin y C #.

Aquí hay un enlace que explica esta característica en Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

Otros lenguajes, como Java y Scala, tienen Optionals, pero funcionan de manera diferente a Optionals en Swift, porque los tipos de Java y Scala son anulables por defecto.

En general, creo que esta característica debería haber sido nombrada Nullableen Swift, y no Optional...

Eh, tú
fuente
0

Implicitly Unwrapped Optionales un azúcar sintáctico Optionalque no obliga a un programador a desenvolver una variable. Se puede usar para una variable que no se puede inicializar durante two-phase initialization processe implica que no es nula. Esta variable se comporta como no nula pero en realidad es una variable opcional . Un buen ejemplo es: puntos de venta de Interface Builder

Optional generalmente son preferibles

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
yoAlex5
fuente