Apple ha appena rilasciato la versione 5 del nostro linguaggio di programmazione preferito, introducendo diverse interessanti novità.

Vediamole insieme 💪

ABI Stability

La novità più importante consiste nell’aver introdotto l’ABI Stability (ho accennato l’argomento in questo articolo).

Ma, che vantaggi porterà a noi developers?

D’ora in poi, un’app compilata con Swift 5 potrà comunicare con una libreria compilata con un’altra versione di Swift.

In passato potrà esserti capitato che, dopo un’aggiornamento di Swift, la tua applicazione che sfruttava CocoaPods non compilasse più, costringendoti ad aggiornare manualmente i Pods.

Questo scenario in futuro non potrà mai più ripresentarsi!

Inoltre, le app presenti sull’App Store compilate con il linguaggio Swift 5 avranno una dimensione minore perché non incorporeranno più le Swift standard libraries, in quanto saranno incluse in iOS.

String literals

Swift 5 introduce un nuovo modo di creare le stringhe, usando il simbolo # come delimitatore.

Questo vuol dire che, non dovrai più usare il backslash (il carattere “\”) per inserire alcuni caratteri speciali (come ad esempio \n, \t, \”).

Nel caso in cui dovessimo inserire un asterisco all’interno di una stringa, basterà delimitarla con un asterisco in più.

Vediamo alcuni esempi:

print(#""I think the things you regret most in life are the things you didn’t do." - Steve Jobs"#)

print(##"Swift 5 is #awesome"##)

print(#"One plus one is \#(1 + 1)"#)

let regex = #"https:\/\/(www\.)*xcoding\.it"#

Il tipo Result

Questo nuovo tipo è un enum composto da due cases: success e failure, entrambi implementati usando i generics.

Tuttavia, failure dovrà essere conforme al tipo Error. Questo nuovo tipo potrebbe aiutarti a gestire meglio gli errori in una situazione complessa, come ad esempio una chiamata ad una API.

Facciamo un esempio:

Ipotizza di dover implementare una chiamata ad un servizio che ti restituisce il saldo del tuo conto corrente.

enum NetworkError: Error {
    case unauthorized
}

func getBalance(completion: @escaping (Result<Decimal, NetworkError>) -> Void) {
    // ...
    URLSession.shared.dataTask(with: request) { data, response, error in
        guard response.statusCode != 401 else {
            completion(.failure(.unauthorized))
        }
    
        let balance: Decimal = getBalance(fromData: data)
        completion(.success(data))
    }
}

getBalance() { result in
    switch result {
        case .success(let balance):
            print("Your balance: \(balance) €.")
        case .failure(let error):
            if error == .unauthorized {
                print("Unauthorized!")
            }
    }
}

String interpolation

Debuggando, ti sarà sicuramente capitato di stampare un oggetto in console, ed avrai notato che, nel caso di una classe, non vengano restituite informazioni particolarmente interessanti.

Per ovviare a questo problema, potrai estendere il metodo appendInterpolation della classe String.StringInterpolation aggiungendo le tue classi:

class Person {
    var name: String
    var surname: String
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Person) {
        appendInterpolation("\(value.name) \(value.surname)")
    }
}

Ma qual è la differenza con CustomStringConvertible?

È semplice, con appendInterpolation potrai aggiungere tutti i parametri che ti serviranno:

extension Person {
    enum PrintStyle {
        case full
        case nameOnly
    }
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Person, printStyle: Person.PrintStyle) {
        if printStyle == full {
            appendInterpolation("\(value.name) \(value.surname)")
        } else {
            appendInterpolation("\(value.name)")
        }
    }
}

Gestire enum futuri

In passato, quando ti trovavi a gestire degli enum con possibili sviluppi futuri, avresti utilizzato la keyword default.

Ora invece, grazie all’introduzione della keyword @unknown default, hai la possibilità di gestire i casi di enum ad oggi inesistenti, triggerando un warning in fase di compilazione.

Opzionali nidificati

Finalmente con l’utilizzo della keyword try? in Swift 5 non ci verrà più restituito un doppio opzionale (se non triplo), bensì un singolo opzionale.

Questo potrebbe succederti, ad esempio, se provassi a chiamare un metodo marcato come throws su un oggetto opzionale:

func getBalance(_ person: Person) throws -> Decimal {
    if let bankAccount = person.bankAccount {
        return bankAccount.balance
    } else {
        throw NSError(domain: "it.xcoding.error", code: -102, userInfo: nil)
    }
}

let balance: Decimal = try? getBalance(person) // person in questo caso sarà opzionale

print(type(of: balance) // Swift 4.2 -> Decimal??
print(type(of: balance)) // Swift 5 -> Decimal?

compactMapValues e dizionari

Questo metodo ti permette di creare un nuovo dizionario a partire dall’originale escludendo gli opzionali, ad esempio:

let grades: [String, String] = [
"Emanuele": "18",
"Giorgio": "25",
"Anna": "Non classificato"
]

let passed: [String, Int] = grades.compactMapValues { Int.init }
// il nuovo dizionario creato escluderà Anna

isMultiple(of:)

Questo nuovo metodo ti permette di verificare che un numero sia multiplo di un altro numero, ad esempio:

if myNumber.isMultiple(of: 2) {
    print("Il numero \(myNumber) è pari")
}

Ovviamente, scrivere:

if myNumber % 2 == 0

Sarebbe stata la stessa cosa, ma questo nuovo metodo rende il codice più leggibile.