Píldoras de códigoSwift

Swift: ‘some’ y los tipos opacos de retorno

Descubre qué hay detrás de la sentencia que hace posible SwiftUI

Una de las características que más llamó la atención cuando Apple presentó SwiftUI fue esa sentencia some que aparecía en la propiedad calculada bodyEsta propiedad es necesaria para implementar el protocolo View en el que se basa todo el framework.

Y no es algo exclusivo para SwiftUI si no que es una de las nuevas características introducidas en la versión 5.1 del lenguaje Swift denominada tipos opacos de retorno.

Definición informal

Aunque su uso es relativamente sencillo, al principio se puede atragantar un poco comprender el funcionamiento, y sobre todo el fin que persiguen los tipos opacos de retorno en Swift.

Un tipo opaco se aplica cuando una función (o variable calculada) retorna un protocolo y obliga a que el tipo devuelto sea conocido en tiempo de compilación y siempre sea el mismo.

Es decir, si mi función devuelve some Protocolo y Tipo1 y Tipo2 se conforman a ese protocolo, no puedo devolver unas veces Tipo1 y otras Tipo2. Tengo que devolver siempre el mismo, sea uno u otro. Otra forma de verlo es que el llamante de la función o variable calculada necesita que el tipo no varíe en tiempo de ejecución.

Ejemplo

Si os acaba de explotar la cabeza, al igual que ocurre cuando buscas distintas definiciones por la red, creo que con un pequeño ejemplo resulta más fácil asimilarlo. Tenemos el protocolo Persona y los tipos Pintor y Escritor definidos así:

protocol Persona {
    var nombre: String { get }
}

struct Escritor: Persona {
    let nombre: String
}

struct Pintor: Persona {
    let nombre: String
}

Ahora vamos a crear un tipo de cada y una enumeración que represente las dos opciones. Además seleccionamos una de las opciones (en seguida veremos para qué).

let escritor = Escritor(nombre: "Pedro")
let pintor = Pintor(nombre: "Pablo")

enum Quien {
    case escritor, pintor
}

let quien: Quien = .escritor

Creamos la siguiente propiedad calculada somePersona. Devolverá un tipo conformado a Persona pero, debido a la condición que hemos escrito, puede ser de tipo Pintor o Escritor.

var persona: Persona {
    if quien == .escritor {
        return escritor
    } else {
        return pintor
    }
}

Esto es perfectamente válido, no hay errores al compilar y se ejecuta sin problemas. Pero, ¿qué ocurre si los consumidores de esta propiedad necesitan que el tipo devuelto sea siempre el mismo? En ese caso añadimos some y hacemos que devuelva un tipo opaco.

var somePersona: some Persona {
    if quien == .escritor {
        return escritor
    } else {
        return pintor
    }
}

Y ahora la cosa cambia porque ni compila… El error es «Function declares an opaque return type, but the return statements in its body do not have matching underlying types». Significa básicamente que estamos definiendo como retorno un tipo opaco, pero estamos devolviendo dos tipos diferentes. En cambio, si devolvemos sólo un tipo, el error desaparece:

var somePersona: some Persona {
    escritor
}

Recordemos que en Swift 5.1 también es posible omitir return cuando devolvemos una expresión simple. Veamos cuál es la función de esta característica del lenguaje.

SwiftUI

Empezamos hablando de SwiftUI así que vamos a ver los tipos opacos de retorno en acción con un ejemplo real. Partimos del siguiente código:

struct ContentView: View {
    var body: some View { 
        Text("Hola mundo") 
    }
}

Esto funciona a las mil maravillas pero quiero añadir una condición para que se muestre (o no) el saludo en más idiomas. Para ello tengo que agrupar los Text en un HStack.

struct ContentView: View {
    var másIdiomas: Bool = true
    
    var body: some View {
        if másIdiomas {
            HStack {
                Text("Hola mundo")
                Text("Hello world")
            }
        } else {
            Text("Hola mundo")
        }
    }
}

Y aquí vemos que muestra el mismo error que antes porque una vez devuelvo un Text y otra un HStack y aunque ambos se conforman al protocolo View al tener que ser opaco el tipo que devuelve, siempre tiene que coincidir.

Está claro que podemos hacer de forma sencilla un par de cambios y conseguir lo que queremos. Por ejemplo, esta solución si compilaría:

struct ContentView: View {
    var másIdiomas: Bool = true

    var body: some View { 
        HStack { 
            Text("Hola mundo") 
            if másIdiomas { 
                Text("Hello world") 
            } 
        } 
    }
}

En este caso, HStack recibe uno o varios View y devuelve un tipo que se conforma también a View permitiendo cumplir la condición de los tipos opacos. Como alternativa podemos utilizar también Group que permite agrupar elementos pero sin una posición definida.

¿Por qué?

Quizás os haya pasado que una vez entendido un concepto, la siguiente cuestión es conocer la razón por la cuál existe.

Los tipos opacos complementan a la programación orientada a protocolos, y proporcionan más flexibilidad en casos en los que antes necesitábamos devolver tipos y no podíamos utilizar protocolos.

Por otra parte, debido a la implementación de SwiftUI a la hora de renderizar vistas, seguro que el framework necesita conocer el tipo concreto en tiempo de compilación para poder componer la interfaz.

Debido a esto, como Swift es software libre, Apple aprovechó para introducir la característica en el propio lenguaje y que pudiera beneficiarse toda la comunidad.

Ya sabéis algo más de las características del lenguaje que usamos en el día a día, así que espero que os haya gustado. Un saludo a todos y Good Apple Coding.

Arturo Rivas

Líder técnico especializado en mobile. Analista y desarrollador software. Apasionado de la tecnología, el deporte y la música.

Artículos relacionados

Botón volver arriba