¿Qué es un "valor sin envolver" en Swift?

155

Estoy aprendiendo Swift para iOS 8 / OSX 10.10 siguiendo este tutorial , y el término " valor sin envolver " se usa varias veces, como en este párrafo (en Objetos y clase ):

Cuando trabajas con valores opcionales, puedes escribir? antes de operaciones como métodos, propiedades y subíndice. Si el valor antes de la? es nulo, todo después del? se ignora y el valor de toda la expresión es nulo. De lo contrario, el valor opcional se desenvuelve y todo después de? actúa sobre el valor sin envolver . En ambos casos, el valor de toda la expresión es un valor opcional.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

No lo entiendo, y busqué en la web sin suerte.

¿Qué significa esto?


Editar

De la respuesta de Cezary, hay una ligera diferencia entre la salida del código original y la solución final (probado en el patio de recreo):

Código original

Código original

La solución de Cezary

La solución de Cezary

Las propiedades de la superclase se muestran en la salida en el segundo caso, mientras que hay un objeto vacío en el primer caso.

¿No se supone que el resultado sea idéntico en ambos casos?

Preguntas y respuestas relacionadas: ¿Qué es un valor opcional en Swift?

Bigood
fuente
1
La respuesta de Cezary es acertada. Además, hay un gran libro de introducción a Swift disponible GRATIS en iBooks
Daniel Storm
@DanielStormApps Este iBook es una versión portátil del tutorial vinculado en mi pregunta :)
Bigood

Respuestas:

289

Primero, debe comprender qué es un tipo Opcional. Un tipo opcional básicamente significa que la variable puede ser nil .

Ejemplo:

var canBeNil : Int? = 4
canBeNil = nil

El signo de interrogación indica el hecho de que canBeNilpuede ser nil.

Esto no funcionaría:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

Para obtener el valor de su variable si es opcional, debe desenvolverlo . Esto solo significa poner un signo de exclamación al final.

var canBeNil : Int? = 4
println(canBeNil!)

Su código debería verse así:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Una nota al margen:

También puede declarar opciones opcionales para desenvolver automáticamente utilizando un signo de exclamación en lugar de un signo de interrogación.

Ejemplo:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Entonces, una forma alternativa de arreglar su código es:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

EDITAR:

La diferencia que está viendo es exactamente el síntoma del hecho de que el valor opcional está envuelto . Hay otra capa encima. La versión sin envoltura solo muestra el objeto recto porque, bueno, está sin envolver.

Una comparación rápida de juegos:

patio de recreo

En el primer y segundo casos, el objeto no se desenvuelve automáticamente, por lo que ve dos "capas" ( {{...}}), mientras que en el tercer caso, solo ve una capa ( {...}) porque el objeto se desenvuelve automáticamente.

La diferencia entre el primer caso y los segundos dos casos es que los segundos dos casos le darán un error de tiempo de ejecución si optionalSquareestá configurado en nil. Usando la sintaxis en el primer caso, puede hacer algo como esto:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}
Cezary Wojcik
fuente
1
¡Gracias por la explicación clara! El código incluido en mi pregunta se cita del tutorial de Apple (enlace en la pregunta), y supongo que se supone que funciona tal como está (y lo hace). Sin embargo, su solución final y la original arrojan resultados diferentes (vea mi edición). ¿Cualquier pensamiento?
Bigood
2
Frio. Gran explicación Sin embargo, todavía estoy confundido. ¿Por qué querría usar un valor opcional? ¿Cuándo es útil? ¿Por qué Apple creó este comportamiento y esta sintaxis?
Todd Lehman
1
Es particularmente relevante cuando se trata de propiedades de clase. Swift requiere que todos los métodos no opcionales se inicialicen en la init()función de la clase. Recomiendo leer los capítulos "Lo básico" (cubre opciones), "Inicialización" y "Encadenamiento opcional" en el libro de Swift publicado por Apple.
Cezary Wojcik
2
@ToddLehman Apple creó esto para ayudarlo a escribir código mucho más seguro. Por ejemplo, imagine todas las excepciones de puntero nulo en Java que bloquean un programa porque alguien se olvidó de buscar nulo. O comportamiento extraño en Objective-C porque se olvidó de buscar nulo. Con el tipo opcional no puede olvidarse de tratar con nulo. El compilador te obliga a lidiar con eso.
Erik Engheim
55
@AdamSmith: viniendo de un fondo de Java, definitivamente puedo ver el uso de los opcionales ... pero viniendo también (más recientemente) de un fondo de Objective-C, todavía tengo problemas para ver su uso, porque Objective- C explícitamente le permite enviar mensajes a objetos nulos. Hmm Me encantaría ver a alguien escribir un ejemplo de estos que muestre cómo ayudan a hacer que el código sea más corto y seguro que el código equivalente que no los usa. No digo que no sean útiles; Solo digo que todavía "no lo entiendo".
Todd Lehman
17

La respuesta correcta existente es excelente, pero descubrí que para comprender esto completamente, necesitaba una buena analogía, ya que este es un concepto muy abstracto y extraño.

Entonces, permítanme ayudar a los demás desarrolladores de "cerebro derecho" (pensamiento visual) dándoles una perspectiva diferente además de la respuesta correcta. Aquí hay una buena analogía que me ayudó mucho.

Analogía de envoltura de regalo de cumpleaños

Piense en los opcionales como si fueran regalos de cumpleaños que vienen en envolturas rígidas, duras y de colores.

No sabes si hay algo dentro de la envoltura hasta que desenvuelves el presente, ¡tal vez no haya nada dentro! Si hay algo dentro, podría ser otro regalo más, que también está envuelto y que también puede no contener nada . Incluso podría desenvolver 100 regalos anidados para finalmente descubrir que no había nada más que envolver .

Si el valor de lo opcional no es nil, ahora ha revelado un cuadro que contiene algo . Pero, especialmente si el valor no se escribe explícitamente y es una variable y no una constante predefinida, entonces es posible que deba abrir el cuadro antes de poder saber algo específico sobre lo que hay en el cuadro, como qué tipo es o qué el valor real es

¡¿Qué hay en la caja?! Analogía

Incluso después de desenvolver la variable, aún eres como Brad Pitt en la última escena en SE7EN ( advertencia : spoilers y lenguaje y violencia groseros con una calificación muy R), porque incluso después de desenvolver el presente, estás en la siguiente situación: ahora tienes nil, o una caja que contiene algo (pero no sabes qué).

Puede que sepas el tipo de algo . Por ejemplo, si declara que la variable es tipo, [Int:Any?]entonces sabría que tiene un Diccionario (potencialmente vacío) con subíndices enteros que producen contenidos envueltos de cualquier tipo antiguo.

Es por eso que tratar con tipos de colección (Diccionarios y matrices) en Swift puede ser un poco complicado.

Caso en punto:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)
Tostada Coma
fuente
agradable 😊 😊😊 😊 😊😊 😊 😊😊
SamSol
Me encanta la analogía! Entonces, ¿hay una mejor manera de desenvolver las cajas? en su ejemplo de código
David T.
El gato de Schrödinger está en la caja
Jacksonkr
Gracias por confundirme ... ¿Qué tipo de programador da un regalo de cumpleaños que está vacío de todos modos? un poco malo
delta2flat
@Jacksonkr O no.
amsmath
13

Swift otorga una alta prima a la seguridad tipo. Todo el lenguaje Swift fue diseñado teniendo en cuenta la seguridad. Es uno de los sellos distintivos de Swift y uno que debes recibir con los brazos abiertos. Ayudará en el desarrollo de un código limpio y legible y ayudará a evitar que su aplicación se bloquee.

Todos los opcionales en Swift están demarcados con el ?símbolo. Al establecer ?después del nombre del tipo en el que está declarando como opcional, esencialmente está convirtiendo esto no como el tipo que está antes del ?, sino como el tipo opcional .


Nota: Una variable o tipo noInt es lo mismo que Int?. Son dos tipos diferentes que no pueden operarse entre sí.


Usando opcional

var myString: String?

myString = "foobar"

Esto no significa que esté trabajando con un tipo de String. Esto significa que está trabajando con un tipo de String?(Cadena opcional o Cadena opcional). De hecho, cada vez que intentas

print(myString)

en tiempo de ejecución, se imprimirá la consola de depuración Optional("foobar"). La parte " Optional()" indica que esta variable puede o no tener un valor en tiempo de ejecución, pero resulta que actualmente contiene la cadena "foobar". Esta " Optional()" indicación permanecerá a menos que haga lo que se llama "desenvolver" el valor opcional.

Desenvolver un opcional significa que ahora está convirtiendo ese tipo como no opcional. Esto generará un nuevo tipo y asignará el valor que residía dentro de ese opcional al nuevo tipo no opcional. De esta manera, puede realizar operaciones en esa variable, ya que el compilador garantiza que tiene un valor sólido.


Desenvolver condicionalmente verificará si el valor en el opcional es nilo no. Si no es así nil, habrá una variable constante recién creada a la que se le asignará el valor y se desenvolverá en la constante no opcional. Y a partir de ahí, puede usar de forma segura lo no opcional en el ifbloque.

Nota: Puede darle a su constante sin envoltura condicional el mismo nombre que la variable opcional que está desenvolviendo.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

La opción de desempaquetado condicional es la forma más limpia de acceder al valor de un opcional porque si contiene un valor nulo, todo lo que se encuentre dentro del bloque if let no se ejecutará. Por supuesto, como cualquier instrucción if, puede incluir un bloque else

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

El desenvolvimiento forzado se realiza empleando lo que se conoce como el !operador ("explosión"). Esto es menos seguro pero aún permite que su código se compile. Sin embargo, cada vez que use el operador de explosión, debe estar 1000% seguro de que su variable contiene un valor sólido antes de desenvolverse por la fuerza.

var myString: String?

myString = "foobar"

print(myString!)

Lo anterior es un código Swift completamente válido. Imprime el valor myStringque se estableció como "foobar". El usuario lo vería foobarimpreso en la consola y eso es todo. Pero supongamos que el valor nunca se estableció:

var myString: String?

print(myString!) 

Ahora tenemos una situación diferente en nuestras manos. A diferencia de Objective-C, cada vez que se intenta desenvolver por la fuerza un opcional, y el opcional no se ha configurado nil, es decir , cuando intenta desenvolver el opcional para ver qué hay dentro de su aplicación se bloqueará.


Desenvoltura con fundición tipo . Como dijimos anteriormente, si bien usted es unwrappingel opcional, en realidad está convirtiendo a un tipo no opcional, también puede convertir el no opcional a un tipo diferente. Por ejemplo:

var something: Any?

En algún lugar de nuestro código, la variable somethingse establecerá con algún valor. Tal vez estamos usando genéricos o tal vez hay otra lógica que está causando que esto cambie. Entonces, más adelante en nuestro código, queremos usar, somethingpero aún así podemos tratarlo de manera diferente si es un tipo diferente. En este caso, querrá usar la aspalabra clave para determinar esto:

Nota: El asoperador es cómo escribe cast en Swift.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Observe la diferencia entre las dos aspalabras clave. Como antes, cuando desenvolvimos a la fuerza un opcional, utilizamos el !operador de explosión para hacerlo. Aquí harás lo mismo, pero en lugar de lanzarlo como no opcional, también lo lanzarás como Int. Y debe poder ser abatido ya que Int, de lo contrario, es como usar el operador de explosión cuando el valor es que nilsu aplicación fallará.

Y para utilizar estas variables en algún tipo de operación matemática, deben desenvolverse para hacerlo.

Por ejemplo, en Swift solo se pueden operar entre sí tipos de datos de números válidos del mismo tipo. Cuando emite un tipo con el as!, está forzando el rechazo de esa variable como si estuviera seguro de que es de ese tipo, por lo tanto, es seguro operar y no bloquear su aplicación. Esto está bien siempre y cuando la variable sea del tipo al que la estás enviando, de lo contrario tendrás un desastre en tus manos.

Sin embargo, la transmisión con as!permitirá que su código se compile. Al lanzar con un as?es una historia diferente. De hecho, as?declara su Intcomo un tipo de datos completamente diferente todos juntos.

Ahora es Optional(0)

Y si alguna vez trataste de hacer tu tarea escribiendo algo como

1 + Optional(1) = 2

Es probable que tu profesor de matemáticas te haya dado una "F". Lo mismo con Swift. Excepto que Swift preferiría no compilar en absoluto en lugar de darle una calificación. Porque al final del día, lo opcional puede ser nulo .

Safety First Kids.

Brandon A
fuente
Buena explicación de los tipos opcionales. Me ayudó a aclarar las cosas.
Tobe_Sta
0

'?' significa expresión de encadenamiento opcional,

el '!' significa fuerza-valor
ingrese la descripción de la imagen aquí

gkgy
fuente