Novedades en Swift 5.2

2
566
    1. Introducción
    2. Expresiones de keypath como funciones
    3. Valores invocables de tipos nominales
    4. Los subscript con argumentos predeterminados
    5. El orden de filtrado diferido ha cambiado
    6. Diagnósticos nuevos
    7. Conclusiones
    8. Referencias

Introducción

Swift 5.2 llegó con Xcode 11.4 e incluye un puñado de cambios de idioma junto con reducciones en el tamaño del código y el uso de memoria, además de una nueva arquitectura de diagnóstico que lo ayudará a comprender y resolver los errores más rápido. En este artículo, analizaré lo que las novedades en Swift 5.2 con algunos ejemplos prácticos para que se pueda ver por sí mismo cómo han evolucionado las cosas. Te animo a que sigas los enlaces a las propuestas de Swift Evolution para obtener más información.

Expresiones de key path como funciones

SE-0249 introdujo un maravilloso acceso directo que nos permite usar las rutas de teclado en un puñado de circunstancias específicas.

La propuesta de Evolution describe esto como ser capaz de usar «\Root.value donde Root -> Value está permitido», pero lo que significa es que se puede recuperar una propiedad de un objeto usarlo como key path: Car.licensePlate

Esto se entiende mejor como un ejemplo, así que aquí hay un tipo User que define cuatro propiedades:

struct User {
    let name: String
    let age: Int
    let bestFriend: String?

    var canVote: Bool {
        age >= 18
    }
}

Podríamos crear alguna instancia de esa estructura y ponerla en una matriz, como esta:

let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]

Ahora para la parte importante: si deseas obtener una matriz de todos los nombres de usuarios, puedes hacerlo utilizando una key path como esta:

let userNames = users.map(\.name)
print(userNames)

Anteriormente, habría tenido que escribir un cierre para recuperar el nombre a mano, así:

let oldUserNames = users.map { $0.name }

Este mismo enfoque funciona en otro lugar: en cualquier lugar donde anteriormente hubiera recibido un valor y pasado una de sus propiedades, ahora puede usar una key path (ruta clave). Por ejemplo, esto devolverá a todos los usuarios que puedan votar:

let voters = users.filter(\.canVote)

Y esto devolverá a los mejores amigos para todos los usuarios que tengan uno:

let bestFriends = users.compactMap(\.bestFriend)

Valores invocables de tipos nominales definidos por el usuario

SE-0253 introduce valores invocables estáticamente a Swift, que es una forma elegante de decir que ahora puedes llamar a un valor directamente si tu tipo implementa un método llamado callAsFunction(). No necesita ajustarse a ningún protocolo especial para que este comportamiento funcione; solo se necesita agregar ese método a tu tipo.

Por ejemplo, podríamos crear una estructura que tenga propiedades para lowerBound y upperBound, y luego añadir callAsFunction para que cada vez que llames a un valor de Dice obtengas una tirada aleatoria:

struct Dice {
    var lowerBound: Int
    var upperBound: Int

    func callAsFunction() -> Int {
        (lowerBound...upperBound).randomElement()!
    }
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)

Eso imprimirá un número aleatorio del 1 al 6, y es idéntico a solo usarlo callAsFunction() directamente. Por ejemplo, podríamos llamarlo así:

let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)

Swift adapta automáticamente sus sitios de llamadas en función de cómo callAsFunction() se define. Por ejemplo, puedes agregar tantos parámetros como desees, puedes controlar el valor de retorno e incluso puedes marcar métodos como mutating si es ​​necesario.

Por ejemplo, una estructura StepCounter que rastrea lo lejos que alguien ha caminado e informa si alcanzó su objetivo de 10,000 pasos:

struct StepCounter {
    var steps = 0

    mutating func callAsFunction(count: Int) -> Bool {
        steps += count
        print(steps)
        return steps > 10_000
    }
}

var steps = StepCounter()
let targetReached = steps(count: 10)

Para un uso más avanzado, callAsFunction() admite ambos throws y rethrows, e incluso puedes definir varios callAsFunction() métodos en un solo tipo: Swift elegirá el correcto según los parámetros de la llamada, al igual que la sobrecarga regular.

Los subscript ahora pueden declarar argumentos predeterminados

Al agregar los subscript personalizados a un tipo, ahora puedes usar argumentos predeterminados para cualquiera de los parámetros. Por ejemplo, si tuviéramos una estructura PoliceForce  con un subscript personalizado para leer a los oficiales de la fuerza, podríamos agregar un default parámetro para enviar de vuelta si alguien intenta leer un índice fuera de los límites del array:

struct PoliceForce {
    var officers: [String]

    subscript(index: Int, default default: String = "Unknown") -> String {
        if index >= 0 && index < officers.count {
            return officers[index]
        } else {
            return `default`
        }
    }
}

let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])

Esto imprimirá «Amy» y luego «Uknown», y esto último se debe a que no hay un oficial en el índice 5. Ten en cuenta que debes escribir sus etiquetas de parámetros dos veces si deseas que se usen, porque los subscript no usan etiquetas de parámetros de lo contrario.

Entonces, como uso default default en mi subscript, puedo usar un valor personalizado como este:

print(force[-1, default: "The Vulture"])

El orden de filtrado diferido ahora se invierte

Hay un pequeño cambio en Swift 5.2 que podría causar que su funcionalidad se rompa: si usas una secuencia lazy como una matriz y le aplicas varios filtros, esos filtros ahora se ejecutan en el orden inverso.

Por ejemplo, este código a continuación tiene un filtro que selecciona nombres que comienzan con S, luego un segundo filtro que imprime el nombre y luego devuelve verdadero:

let people = ["Arya", "Cersei", "Samwell", "Stannis"]
    .lazy
    .filter { $0.hasPrefix("S") }
    .filter { print($0); return true }
_ = people.count

En Swift 5.2 y versiones posteriores, se imprimirán «Samwell» y «Stannis», porque después de que se ejecuta el primer filtro, esos son los únicos nombres que quedan para entrar en el segundo filtro. Pero antes de Swift 5.2, habría devuelto los cuatro nombres, porque el segundo filtro se habría ejecutado antes que el primero. Esto fue confuso, porque si eliminabas el lazy código, el código siempre devolvería solo Samwell y Stannis, independientemente de la versión Swift.

Esto es particularmente problemático porque su comportamiento depende de dónde se esté ejecutando el código: si ejecuta el código Swift 5.2 en iOS 13.3 o anterior, o macOS 10.15.3 o anterior, obtendrás el antiguo comportamiento hacia atrás, pero el mismo código que se ejecuta en los sistemas operativos más nuevos dará el nuevo comportamiento correcto.

Por lo tanto, este es un cambio que puede causar interrupciones sorpresivas en su código, pero con suerte es solo un inconveniente a corto plazo.

Diagnósticos nuevos y mejorados

Swift 5.2 introdujo una nueva arquitectura de diagnóstico que tiene como objetivo mejorar la calidad y la precisión de los mensajes de error emitidos por Xcode cuando comete un error de código. Esto es particularmente evidente cuando se trabaja con código SwiftUI, donde Swift a menudo produciría mensajes de error falsos positivos.

Por ejemplo, un código como este:

struct ContentView: View {
    @State private var name = 0

    var body: some View {
        VStack {
            Text("What is your name?")
            TextField("Name", text: $name)
                .frame(maxWidth: 300)
        }
    }
}

Eso intenta vincular una TextField vista a una @State propiedad de tipo Int, que no es válida. En Swift 5.1, esto causó un error para el frame() modificador que decía ‘Int’ no es convertible a ‘CGFloat?’ , pero en Swift 5.2 y versiones posteriores, esto identifica correctamente que el error es el $name enlace: No se puede convertir el valor de tipo Binding<Int> al tipo de argumento esperado Binding <String> .

Puede encontrar más información sobre la nueva arquitectura de diagnóstico en el blog Swift.org .

Conclusiones

El proyecto Swift ha alcanzado un hito crítico de madurez de los fundamentos básicos, proporcionando estabilidad para que los usuarios inviertan en usar Swift en serio. En las plataformas de Apple como macOS e iOS, la llegada de ABI y la estabilidad del módulo ha permitido la creación de los frameworks binarios estables. Además, Swift Package Manager, que tiene soporte integrado tanto en Xcode como en otros IDEs, proporciona una solución multiplataforma para construir y distribuir bibliotecas Swift. En conjunto, estos forman los ingredientes críticos para fomentar el desarrollo de un floreciente ecosistema de software Swift.

Referencias

  1. Swift.org blog
  2. Apple Swift repo
  3. New Diagnostic Architecture
  4. Swift 5.2 Release changes

 

2 COMENTARIOS

  1. Hola Anton, me preguntaba si me podia ayudar 🙂 estoy bastante desesperando con el tema…

    el caso es que tengo mi projecto en objective c y un fichero en swift. al ejectutar
    _swipFichero = [SwiftFile new];

    me devuelve correctamente los print, el problema es quiero hacer los prints desde mi ViewController.h asi lo puedo poner en un label los datos, me pueden ayudar pr favor? estoy desesperado y no se a quien acudir! gracias y disculpad las molestias!

    let location = CLLocation(latitude: 55.751244, longitude: 37.618423) // Moscow

    public var moonPhaseManager: EKAstrologyCalc!

    override init() {

    moonPhaseManager = EKAstrologyCalc(location: location)

    let info = moonPhaseManager.getInfo(date: Date())

    print(«Current localtion: -«, info.location.coordinate)

    print(«Moon days at», «current date: -«, info.date)
    info.moonModels.forEach {
    print(«===========»)
    print(«Moon Age: -«, $0.age)
    print(«Moon rise: -«, $0.begin ?? Date.self)
    print(«Moon set: -«, $0.finish ?? Date.self)
    print(«Zodiac: -«, $0.sign)
    }
    print(«===========»)
    print(«Moon phase: -«, info.phase)
    print(«Moon trajectory: -«, info.trajectory)
    //print(«Ilumination: -«, info.illumination)

    }

    • Hola Francesc, como lo he entendido, ¿quieres pasar los datos a tu ViewController? Si es así podrías crear un método en la clase SwiftFile(?) que te devuelva los datos que necesites (por ejemplo, el objeto info) sin utilizar los prints. Si no te he entendido bien, lo siento, pero es muy difícil de entenderlo sin mirar el código. Un saludo

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad