Esta respuesta es wiki comunitaria . Si crees que podría mejorarse, ¡puedes editarlo !
Antecedentes: ¿Qué es un opcional?
En Swift, Optional
es un tipo genérico que puede contener un valor (de cualquier tipo), o ningún valor en absoluto.
En muchos otros lenguajes de programación, a menudo se usa un valor "centinela" particular para indicar la falta de un valor . En Objective-C, por ejemplo, nil
(el puntero nulo ) indica la falta de un objeto. Pero esto se vuelve más complicado cuando se trabaja con tipos primitivos: ¿debería -1
usarse para indicar la ausencia de un número entero, o tal vez INT_MIN
, o algún otro número entero? Si se elige cualquier valor particular para que signifique "sin número entero", eso significa que ya no se puede tratar como un valor válido .
Swift es un lenguaje de tipo seguro, lo que significa que el lenguaje le ayuda a tener claridad sobre los tipos de valores con los que puede trabajar su código. Si parte de su código espera una Cadena, el tipo de seguridad le impide pasar un Int por error.
En Swift, cualquier tipo puede hacerse opcional . Un valor opcional puede tomar cualquier valor del tipo original o el valor especial nil
.
Los opcionales se definen con un ?
sufijo en el tipo:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
La falta de un valor en un opcional se indica mediante nil
:
anOptionalInt = nil
(Tenga en cuenta que esto nil
no es lo mismo que nil
en Objective-C. En Objective-C, nil
es la ausencia de un puntero de objeto válido ; en Swift, los opcionales no están restringidos a objetos / tipos de referencia. Opcional se comporta de manera similar a Haskell's Maybe ).
¿Por qué recibí " error fatal: inesperadamente encontrado nulo al desenvolver un valor opcional "?
Para acceder al valor de un opcional (si tiene uno), debe desenvolverlo . Un valor opcional se puede desenvolver de forma segura o forzada. Si desenvuelve a la fuerza un opcional, y no tenía un valor, su programa se bloqueará con el mensaje anterior.
Xcode le mostrará el bloqueo resaltando una línea de código. El problema ocurre en esta línea.
Este bloqueo puede ocurrir con dos tipos diferentes de desenvolvimiento forzado:
1. Desenvolvimiento de fuerza explícito
Esto se hace con el !
operador de forma opcional. Por ejemplo:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Error fatal: inesperadamente encontrado nulo al desenvolver un valor opcional
Como anOptionalString
está nil
aquí, obtendrá un bloqueo en la línea donde fuerza desenvolverlo.
2. Opciones opcionales implícitamente sin envolver
Estos se definen con un !
, en lugar de un ?
después del tipo.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Se supone que estos opcionales contienen un valor. Por lo tanto, siempre que acceda a una opción sin envoltura implícita, automáticamente se desenvolverá forzosamente para usted. Si no contiene un valor, se bloqueará.
print(optionalDouble) // <- CRASH
Error grave : se encontró inesperadamente nulo al desenvolver implícitamente un valor opcional
Para determinar qué variable causó el bloqueo, puede mantener presionada ⌥mientras hace clic para mostrar la definición, donde puede encontrar el tipo opcional.
Los IBOutlets, en particular, generalmente son opciones implícitas sin envolver. Esto se debe a que su xib o storyboard vincularán las salidas en tiempo de ejecución, después de la inicialización. Por lo tanto, debe asegurarse de que no está accediendo a los puntos de venta antes de que se carguen. También debe verificar que las conexiones sean correctas en su archivo storyboard / xib, de lo contrario, los valores estarán nil
en tiempo de ejecución y, por lo tanto, se bloquearán cuando se desenvuelvan implícitamente . Al arreglar las conexiones, intente eliminar las líneas de código que definen sus puntos de venta, luego vuelva a conectarlos.
¿Cuándo debería forzar el desenvolvimiento de un opcional?
Desenvolvimiento de fuerza explícito
Como regla general, nunca debe forzar explícitamente el desenvolvimiento de un opcional con el !
operador. Puede haber casos en los que el uso !
sea aceptable, pero solo debería usarlo si está 100% seguro de que lo opcional contiene un valor.
Si bien no puede ser una ocasión donde se puede utilizar la fuerza de desenvolver, como usted sabe que es un hecho de que un opcional contiene un valor - no hay un solo lugar donde no se puede con seguridad que desenvuelva opcional en lugar.
Opciones implícitas sin envolver
Estas variables están diseñadas para que pueda diferir su asignación hasta más adelante en su código. Es su responsabilidad asegurarse de que tengan un valor antes de acceder a ellos. Sin embargo, debido a que implican el desenvolvimiento forzado, siguen siendo inherentemente inseguros, ya que suponen que su valor no es nulo, aunque la asignación de nulo es válida.
Solo debe usar opciones opcionales implícitamente sin envolver como último recurso . Si puede usar una variable perezosa o proporcionar un valor predeterminado para una variable, debe hacerlo en lugar de usar una opción implícitamente desenvuelta.
Sin embargo, hay algunos escenarios en los que las opciones opcionales sin envoltura son beneficiosas , y aún puede usar varias formas de desenvolverlas de manera segura como se enumera a continuación, pero siempre debe usarlas con la debida precaución.
¿Cómo puedo tratar con seguridad los opcionales?
La forma más sencilla de verificar si un opcional contiene un valor, es compararlo con nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Sin embargo, el 99.9% del tiempo cuando trabaje con opciones, en realidad querrá acceder al valor que contiene, si es que contiene uno. Para hacer esto, puede usar Enlace opcional .
Enlace opcional
El enlace opcional le permite verificar si un opcional contiene un valor, y le permite asignar el valor sin envolver a una nueva variable o constante. Utiliza la sintaxis if let x = anOptional {...}
o if var x = anOptional {...}
, dependiendo de si necesita modificar el valor de la nueva variable después de vincularla.
Por ejemplo:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Lo que esto hace es comprobar primero que el opcional contiene un valor. Si lo hace , el valor 'sin envolver' se asigna a una nueva variable ( number
), que luego puede usar libremente como si no fuera opcional. Si el opcional no contiene un valor, se invocará la cláusula else, como era de esperar.
Lo bueno de la encuadernación opcional es que puede desenvolver múltiples opcionales al mismo tiempo. Simplemente puede separar las declaraciones con una coma. La declaración tendrá éxito si todas las opciones se desenvolvieran.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Otro buen truco es que también puede usar comas para verificar una determinada condición en el valor, después de desenvolverlo.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
El único inconveniente con el uso del enlace opcional dentro de una instrucción if es que solo puede acceder al valor sin envolver dentro del alcance de la instrucción. Si necesita acceder al valor desde fuera del alcance de la declaración, puede usar una declaración de protección .
Una declaración de protección le permite definir una condición para el éxito, y el alcance actual solo continuará ejecutándose si se cumple esa condición. Se definen con la sintaxis guard condition else {...}
.
Entonces, para usarlos con un enlace opcional, puede hacer esto:
guard let number = anOptionalInt else {
return
}
(Tenga en cuenta que dentro del cuerpo de guardia, debe usar una de las declaraciones de transferencia de control para salir del alcance del código que se está ejecutando actualmente).
Si anOptionalInt
contiene un valor, se desenvolverá y se asignará a la nueva number
constante. El código después del guardia continuará ejecutándose. Si no contiene un valor, el guardia ejecutará el código entre paréntesis, lo que conducirá a la transferencia de control, de modo que el código inmediatamente posterior no se ejecutará.
Lo realmente bueno de las declaraciones de guardia es que el valor sin envolver ahora está disponible para usar en el código que sigue a la declaración (ya que sabemos que el código futuro solo puede ejecutarse si el opcional tiene un valor). Esto es ideal para eliminar las 'pirámides de la fatalidad' creadas al anidar múltiples sentencias if.
Por ejemplo:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Los guardias también admiten los mismos trucos que la declaración if admite, como desenvolver múltiples opciones al mismo tiempo y usar la where
cláusula.
Si usa una declaración if o guard depende completamente de si algún código futuro requiere que lo opcional contenga un valor.
Operador de fusión nula
El Operador de fusión nula es una ingeniosa versión abreviada del operador condicional ternario , diseñado principalmente para convertir opcionales en no opcionales. Tiene la sintaxis a ?? b
, donde a
es un tipo opcional y b
es del mismo tipo que a
(aunque generalmente no es opcional).
Básicamente te permite decir "Si a
contiene un valor, desenvuélvelo. Si no es así, regrese b
en su lugar ". Por ejemplo, podría usarlo así:
let number = anOptionalInt ?? 0
Esto definirá una number
constante de Int
tipo, que contendrá el valor de anOptionalInt
, si contiene un valor, o de lo 0
contrario.
Es solo una abreviatura de:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Encadenamiento opcional
Puede usar el encadenamiento opcional para llamar a un método o acceder a una propiedad en un opcional. Esto se hace simplemente con el sufijo del nombre de la variable con a ?
cuando se usa.
Por ejemplo, supongamos que tenemos una variable foo
, de tipo una Foo
instancia opcional .
var foo : Foo?
Si quisiéramos llamar a un método foo
que no devuelve nada, simplemente podemos hacer:
foo?.doSomethingInteresting()
Si foo
contiene un valor, se llamará a este método. Si no es así, no pasará nada malo: el código simplemente continuará ejecutándose.
(Este es un comportamiento similar al envío de mensajes nil
en Objective-C)
Por lo tanto, esto también se puede utilizar para establecer propiedades y métodos de llamada. Por ejemplo:
foo?.bar = Bar()
De nuevo, nada malo va a pasar aquí si foo
es nil
. Su código simplemente continuará ejecutándose.
Otro buen truco que el encadenamiento opcional le permite hacer es verificar si establecer una propiedad o llamar a un método fue exitoso. Puede hacer esto comparando el valor de retorno con nil
.
(Esto se debe a que un valor opcional devolverá en Void?
lugar de Void
en un método que no devuelve nada)
Por ejemplo:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Sin embargo, las cosas se vuelven un poco más difíciles cuando se trata de acceder a propiedades o métodos de llamada que devuelven un valor. Como foo
es opcional, todo lo que devuelva también será opcional. Para lidiar con esto, puede desenvolver las opciones que se devuelven utilizando uno de los métodos anteriores, o desenvolverse foo
antes de acceder a los métodos o llamar a los métodos que devuelven valores.
Además, como su nombre lo indica, puede 'encadenar' estas declaraciones juntas. Esto significa que si foo
tiene una propiedad opcional baz
, que tiene una propiedad qux
, puede escribir lo siguiente:
let optionalQux = foo?.baz?.qux
Nuevamente, porque foo
y baz
son opcionales, el valor devuelto desde qux
siempre será opcional independientemente de si qux
es opcional.
map
y flatMap
Una característica a menudo infrautilizada con opciones es la capacidad de usar las funciones map
y flatMap
. Estos le permiten aplicar transformaciones no opcionales a variables opcionales. Si un opcional tiene un valor, puede aplicarle una transformación determinada. Si no tiene un valor, permanecerá nil
.
Por ejemplo, supongamos que tiene una cadena opcional:
let anOptionalString:String?
Al aplicarle la map
función, podemos usar la stringByAppendingString
función para concatenarla en otra cadena.
Debido a que stringByAppendingString
toma un argumento de cadena no opcional, no podemos ingresar nuestra cadena opcional directamente. Sin embargo, al usar map
, podemos usar permitir stringByAppendingString
que se use si anOptionalString
tiene un valor.
Por ejemplo:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Sin embargo, si anOptionalString
no tiene un valor, map
volverá nil
. Por ejemplo:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
funciona de manera similar a map
, excepto que le permite devolver otro opcional desde el cuerpo del cierre. Esto significa que puede ingresar un opcional en un proceso que requiere una entrada no opcional, pero puede generar un opcional en sí mismo.
try!
El sistema de manejo de errores de Swift se puede usar de manera segura con Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Si someThrowingFunc()
arroja un error, el error quedará atrapado con seguridad en el catch
bloque.
La error
constante que ve en el catch
bloque no ha sido declarada por nosotros, se genera automáticamente por catch
.
También puede declararse error
, tiene la ventaja de poder convertirlo a un formato útil, por ejemplo:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Usar try
esta forma es la forma correcta de intentar, atrapar y manejar los errores que provienen de las funciones de lanzamiento.
También hay try?
que absorbe el error:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Pero el sistema de manejo de errores de Swift también proporciona una manera de "forzar el intento" con try!
:
let result = try! someThrowingFunc()
Los conceptos explicados en esta publicación también se aplican aquí: si se produce un error, la aplicación se bloqueará.
Solo debe usarlo try!
si puede demostrar que su resultado nunca fallará en su contexto, y esto es muy raro.
La mayoría de las veces usará el sistema completo Do-Try-Catch, y el opcional try?
, en los raros casos en los que manejar el error no es importante.
Recursos