TableView ed il database Realtime di Firebase. Come metterli in relazione dinamicamente

Come si mette in relazione una TableView ed il database Realtime di Firebase?

Creare e popolare una TableView con il linguaggio Swift è un passaggio relativamente semplice. Tutto si complica quando il riempimento della tabella deve avvenire in seguito al download asincrono di un contenuto (Ho parlato dei concetti di asincronia e thread in questo articolo).

Oggi te ne voglio parlare con esempio applicato al framework di Firebase.

Quello che noi faremo è relativamente semplice (a parole). L’app si connetterà al database realtime di Firebase e scaricherà una lista di elementi. Contemporaneamente all’iterazione degli elementi della lista, li trasformerà in oggetti malleabili con il linguaggio Swift e li aggiungerà alla TableView.

firebase-databaserealtime-e-tableview-ios-linguaggio-swift

Quindi, poco spazio alle parole, sei pronto?
Allora vediamo insieme come gestire una TableView ed il database Realtime di Firebase!

Il progetto

La nostra applicazione d’esempio è un raccoglitore di figure professionali. L’interfaccia è composto da un UIViewController che ha, al suo interno, una UITableView con una Prototype Cell con Style Subtitle. Il ViewController, infine, è collegato ad un UINavigationController (usato solo per ottenere la top bar).

tableview-ios-linguaggio-swift

Uno specialista è rappresentato da una struct che ci permetterà di gestire meglio la mole di dati trasferita da e verso Firebase. Quindi, assicurati di avere una struct o classe del genere:

struct Specialist {
    var name: String
    var profession: String
}

Per quanto riguarda la gestione della tabella, nel ViewController, crea un array vuoto del tipo [Specialist], crea la outlet tra l’interfaccia e la TableView, definisci il DataSource ed aggiungi i relativi metodi. Questo è il codice che dovresti avere:

import UIKit
import FirebaseDatabase

class ViewController: UIViewController, UITableViewDataSource {
    
    var specialists: [Specialist] = []


    @IBOutlet var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.tableView.dataSource = self
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.specialists.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        let user = self.specialists[indexPath.row]
        
        cell.textLabel?.text = user.name
        cell.detailTextLabel?.text = user.profession
        
        return cell
    }

}

In fondo alla pagina trovi il download del progetto completo.

Non dimenticare di importare il file GoogleService-Info.plist, di sbloccare la lettura/scrittura dal Database Realtime e tutte le altre sfaccettature che ho spiegato nel tutorial introduttivo al Database Realtime di Firebase.

Una volta definita la struttura dell’app, quella su Firebase sarà un semplice nodo con sotto nodi generati dinamicamente (childByAutoId) i quali conterranno i dati del professionista. Quindi, se vuoi seguire per filo e per segno il tutorial, assicurati di avere una struttura simile a questa:

struttura-database-realtime-firebase

Una volta definita la base dei nostri dati, vediamo come scaricarli ed inserirli nella tabella.

Download con observe(.value)

Procediamo con ordine ed analizziamo i vari sistemi con cui potrai scaricare i dati da Firebase. Il primo è indubbiamente quello di utilizzare un FIRDataEventType di tipo value.

Ti ricordo che un observer, di tipo .value, scarica il contenuto dell’intero nodo in cui si attacca. Il download è in differita. Ovvero il download avviene in asincrono, su una queue in background, ed il dato viene conservato in un oggetto di tipo FIRDataSnapshot restituito dalla closure (o per chi proviene dal web, una callback).

let root = FIRDatabase.database().reference()

root.child("specialists").observe(.value, with: { (snap) in
   // eseguito sulla DispatchQueue.main al termine del download
})

Quindi, il download del contenuto avviene su una queue di background, mentre la closure viene invocata sul main thread.

Se questi concetti ti sembrano nuovi, dai un’occhiata al mio tutorial sul Grand Central Dispatch prima di procedere.

Dato che l’observer con event type value restituisce tutto il contenuto dei nodi e sotto nodi, leggendo sul nodo specialists, dovremmo aver scaricato un dizionario. Un dizionario cui:

  • la chiave è la stringa univoca che rappresenta il sotto nodo
  • il valore associato è il dizionario con chiave name e profession

Il download avviene per intero, quindi, è lecito pensare che il passaggio dei dati in tabella debba avvenire tutto in una volta.  Questo significa che dovrai creare un array temporaneo, del tipo Specialist, a cui andrai ad inserire tutti i dati estrapolati dal dizionario scaricato.

Una volta completato questo passaggio, potrai aggiornare la tabella dal DispatchQueue.main.async:

root.child("specialists").observe(.value, with: { (snap) in
    // eseguito sulla main queue non appena finito il download
    
    // converto il contenuto dello snap nel dato che lo rappresenta, cioè un Dizionario con chiave String e valore Any
    guard let dictionary = snap.value as? [String : Any] else {
        return
    }
    
    // creo un array temporaneo per la conversione del dizionario in Specialist
    var specialistArray: [Specialist] = []
    
    for element in dictionary { // itero il dizionario
        
        guard
            let value = element.value as? [String : String], // converto il valore in un dizionario di String:String
            let name = value["name"], // estrapolo il nome
            let profession = value["profession"] // estrapolo la professione
        else { return }
        
        let specialist = Specialist(name: name, profession: profession) // creo un oggetto Specialist
        specialistArray.append(specialist) // lo passo all'array temporaneo
        
    }
    
    self.specialists = specialistArray // assegno l'array temporaneo all'array utilizzato dalla tabella
    self.tableView.reloadData() // aggiorno la tabella
    
})

firebase-e-tableview-ios-linguaggio-swift

Perché tutti quei guard? (se non ti ricordi cos’è, leggi questo mia lezione sul guard)

É sempre un bene estrapolare i valori scaricati utilizzando un’istruzione di tipo guard o if let, anche se a monte conosci già la struttura dei dati. Perché?

Se in futuro dovessi aggiornare la struttura del database, gli utenti che utilizzeranno una versione non ancora aggiornata a quel modello, non avranno crash ma un semplice download non completato (la pagina rimarrà bianca).

Una volta passato l’array temporaneo all’array che hai dato in pasto alla tabella, ricordati di invocare il reloadData() sulla tableView. Questo è un passaggio necessario in quanto richiamerà tutti i metodi per la gestione della tabella (numberOfRowInSection e cellForRowAtIndexPath).

[mailmunch-form id=”101287″]

Nel caso dovessi avere dei problemi, non esitare a chiedere. Chi fa domande è sempre avanti a chi non le fa ;) 

Download con observe(.childAdded)

Spesso, i dati da leggere, vengono modificati continuamente da altri utenti o da noi stessi. Pensa per esempio ad una chat, i dati potrebbero venir tirati fuori uno dietro l’altro nell’arco di poco tempo. Di conseguenze, l’utilizzo di un observer .value è poco consigliato in quanto, qualsiasi modifica al nodo osservato, comporterebbe il download dell’interno blocco.

In questi casi, o in tutti i casi in cui si aspetta l’aggiornamento di un nodo con un nuovo dato, conviene utilizzare l’observe con FIRDataEventType di tipo .childAdded.

L’observe di tipo .childAdded verrà invocato:

  • Al primo avvio per N volte quanti sono i nodi presenti (un po’ come se fossero stati inseriti in quel momento ed uno dopo l’altro)
  • Ogni qual volta in cui verrà aggiunto un nuovo nodo al nodo osservato

Di conseguenza, il FIRDataSnapshot attaccato al nodo specialists, conterrà il dizionario che ha per chiave name e profession. 

root.child("specialists").observe(.childAdded, with: { (snap) in

    // estraggo i dati del professionista
    guard
        let dictionary = snap.value as? [String : String],
        let name = dictionary["name"],
        let profession = dictionary["profession"]
    else { return }
    
    let specialist = Specialist(name: name, profession: profession)
        
    print(specialist)
})

Prima di farti vedere il codice completo, ragioniamo un attimo su quello che accade qui.

L’observe .childAdded, al primo avvio, legge uno per uno tutti i sotto nodi presenti nel nodo specialists. Per cominciare, quindi, lo snap conterrà solamente il sotto nodo “Giuseppe Sapienza“. Dato che ci sono più sotto nodi, una volta completata la prima lettura, verrà invocato un ulteriore observe sul nodo successivo. Continuerà così fin quando non finiranno tutti i sotto nodi.

In pratica, si comporta come una sorta di ciclo for che itera un array. Il contenuto della closure (cioè lo snap) rappresenta l’elemento iterato.

Per questo motivo, è logico pensare che l’inserimento del dato in tabella debba avvenire singolarmente. Dunque, ogni qual volta in cui l’observe tirerà fuori un nuovo nodo, dovrai convertire immediatamente il dato ed inserirlo in tabella. 

L’inserimento di un dato in maniera singola o per range, all’interno di una tabella, si esegue utilizzando la funzione insertRows il quale vuole come parametro un array di IndexPath (cioè di indici) e l’animazione da utilizzare. Questo metodo, deve essere inserito tra le chiamate dei metodi beginUpdates() ed endUpdates() invocati sulla tabelle, i quali eviteranno l’accesso multiplo alle risorse (ti basta sapere che si deve fare così :P ).

Prima di invocarlo, però, è necessario aggiungere il nuovo dato nell‘array utilizzato dalla tabella. Il motivo è che l’insertRows si comporta come un reloadData però invocato solo sugli indici passati. Quindi, se non c’è l’elemento nell’array, i metodi cellForRowAtIndexPath e numberOfRowsInSection genereranno un errore.

root.child("specialists").observe(.childAdded, with: { (snap) in

    // estraggo i dati del professionista
    guard
        let dictionary = snap.value as? [String : String],
        let name = dictionary["name"],
        let profession = dictionary["profession"]
    else { return }

    let specialist = Specialist(name: name, profession: profession)
        
    self.specialists.append(specialist) // aggiunto l'elemento all'array della tabella prima dell'aggiornamento
        
    let count = self.specialists.count // prendo il numero di elementi in tabella
    let indexPath = IndexPath.init(row: count-1, section: 0) // genero l'indexPath in cui andrà il nuovo elemento
        
    self.tableView.beginUpdates() // necessario prima dell'invocazione dell'insertRows
    self.tableView.insertRows(at: [indexPath], with: .left) // aggiunto una nuova riga alla tabella all'indexPath con animazione da left verso right
    self.tableView.endUpdates() // necessario subito dopo l'invocazione dell'insertRows
    
})

Considerazioni

Ho voluto mostrarti i metodi nudi e crudi per avvicinarsi a qualcosa di che nella sua banalità logica – del resto, che ci voleva? – ha qualcosa di molto complesso alle spalle.

Il passo sicuramente successivo è quello di wrappare questi metodi in ulteriori metodi presenti nelle tue classi che gestiscono, in maniera centralizzata, tutta la parte di download dell’applicazione. Ma questo dovrebbe venirti molto più semplice una volta capito il sistema che sta alla base dei metodi di Firebase.

A questo punto, dato che già dovresti avere le basi sull’uso del database realtime, ti indirizzo verso l’integrazione del login con Firebase.

Buona Programmazione!

Download del progetto

Trovi il download del seguente progetto, una volta sbloccato il seguente modulo:

[sociallocker]

Una volta scaricato, dato che non porta con sé i pod, dovrai eseguire un pod update dalla console sulla cartella del progetto. Ricordati anche di inserire il tuo file GoogleService-info.plist

Download Progetto

[/sociallocker]

Changelog

  • 29/03/2017 – Aggiornate alcune parti del tutorial.
  • 29/03/2017 – Prima versione del tutorial.

Grand Central Dispatch con il linguaggio Swift. Alla scoperta dei Thread e della concorrenza

Grand Central Dispatch con il linguaggio Swift. Alla scoperta dei Thread e della concorrenza

Partiamo da un presupposto elementare: il codice viene eseguito dall’alto verso il basso, istruzione per istruzione.

Questa regola, che come tale può essere infranta, sta alla base di qualsiasi linguaggio di programmazione, tra cui anche il nostro linguaggio Swift, e le conseguenze di questo assioma sono tante e non sempre positive.

La prima è quella diretta e scontata che ci permette sempre di conoscere il risultato del codice ancor prima della sua esecuzione (Se il codice parte alla riga n.1 e finisce alla n.3 sai che il risultato sarà la somma di tutte le istruzioni precedenti).

esecuzione lineare del codice swift

Peppe, serviva un tutorial per spiegarmi questo?

Assolutamente no. Però, vorrei portarti ad entrare nel merito di questa ovvietà. Perché da sviluppatori moderni e che utilizzano linguaggi di alto livello come il linguaggio Swift, tendiamo a dimenticare o sconoscere il meccanismo che sta alla base di determinati comportamenti del codice.

Sapevi che ogni istruzione impiega del tempo per essere eseguita? 

Nel caso dell’esempio da cui siamo partiti, cioè i tre print, potevamo essere indotti a pensare che l’esecuzione fosse stata istantanea.

L’istantaneità, per il semplice fatto che il codice viene eseguito da una macchina (che è quindi soggetta alle regole della fisica) è un concetto illusorio. Potremmo dire che è talmente veloce da non farcene accorgere, ma tra una riga e l’altra passa inesorabilmente del tempo.

Il tempo reale d’esecuzione non è esattamente quello per via dell’esecuzione su Playground e di diversi fattori. Però rende bene l’idea.

Ora, riesci ad immaginare cosa succederebbe se una riga di codice, invece di starci meno di un battito di ciglia, impiegasse 4 secondi per terminare la sua esecuzione? 

Per la regola che ho scritto all’inizio, se una riga non termina la sua esecuzione, automaticamente non potranno essere eseguite tutte le altre.

Tradotto: l’app si bloccherà per 4 secondi. Questa è la norma nei download dei contenuti da internet come le immagini, i video o, in generale, tutte quelle operazioni che hanno bisogno di un lasso di tempo per essere eseguite.

Peppe, vuoi dire che la mia app è destinata a bloccarsi?

Assolutamente no.

La risposta a questo problema è quella che mi permetterà di introdurti il Grand Central Dispatch con il linguaggio Swift il quale ti permetterà di eludere questo blocco e di ottimizzare i tempi d’esecuzione del codice della tua applicazione iOS.

Il tutorial è abbastanza lungo. Quindi tu, a differenza delle tue app, hai il diritto di bloccarti su alcune parti (sopratutto l’inizio che è la chiave per capire la parte finale).

Premesse fatta. Sei pronto ad entrare nei meandri oscuri del Grand Central Dispatch con il linguaggio Swift?
Allora cominciamo!

CPU, Processi e Thread

Se oggi i computer sono così semplici da utilizzare lo dobbiamo principalmente ad una persona: John Von Neumann. Neumann, matematico ungherese (poi trasferitosi negli USA), negli anni ’40, ovvero in piena II Guerra Mondiale, teorizzò quella che da lì a breve si apprestò a diventare l’architettura alla base dei computer programmabili.

L’architettura di Von Neumann stabilisce che un computer debba essere composto da quattro componenti:

  1. Dispositivo di IO (Input/Output). Nei nostri bei iPhone è il Display
  2. Memoria centrale (per semplicità, chiamiamola solo RAM). Qui vive l’app quando viene eseguita e qui vivono i dati che non sono persistenti
  3. Memoria di massa (per semplicità, l’hard disk). Il luogo dove risiedono i dati persistenti (quelli che non si perdono al riavvio dell’app o del telefono)
  4. CPU o unità di elaborazione. Il cuore pulsante di ogni computer, qui vengono eseguite tutte le istruzioni di codice
architettura di von_neumann
fonte wikipedia

Per farti entrare nel meccanismo, ti faccio un esempio con il cibo (e ci sta pure).

Fai finta di trovarti in cucina e di voler preparare una succulenta pietanza dal tuo ricettario preferito. Ipotizziamo che tu voglia preparare una carbonara.

Prendi il libro delle ricette (input) che si trova sullo scaffale (memoria di massa), lo sfogli e trovi la tua ricetta (la tua app). La ricetta ti dice solamente quali sono gli ingredienti e come utilizzarli (la ricetta è un modello dell’oggetto reale). Allora tu cominci a prenderli ed a seguire i passi per metterli insieme (stai elaborando gli ingredienti per creare la pietanza – CPU). Una volta creata la pietanza, questa la metti nel piatto che andrà a finire nel tavolo (memoria centrale).

Processo

L’applicazione che tu realizzerai e caricherai sul tuo telefonino è come una ricetta del tuo libro di cucina. Contiene tutto ciò che serve al sistema per poterla creare ed utilizzare.

L’applicazione utilizzata, cioè quella con cui interagiamo ed interagiscono gli utenti, prende il nome di processo. Un processo è l’esecuzione dell’applicazione.

Come per la cucina, la ricetta è una sola (il progetto dell’app è uno) mentre i piatti creati (cioè i processi) potrebbero essere uno o più.

Su iOS è permessa la creazione di un solo processo per ogni singola applicazione. Mentre, così si capisce meglio di che sto parlando, su macOS o su Windows, si possono creare più processi della stessa applicazione.

La stessa applicazione (Safari) con più processi (uno per ogni pagina web). Processo genitore (Safari) processi figli le pagine aperte

Il processo vive nella memoria centrale (la ram). Trovandosi in memoria centrale, tutto ciò che accade qui è soggetto alla volubilità della memoria stessa.

Ovvero una volta ucciso il processo (lo swipe verso l’alto dal task manager) tutti i contenuti delle variabili vengono distrutti. Questo concetto di variabili e memoria l’avevo introdotto velocemente nella lezione sulle variabili e costanti del linguaggio Swift.

Perché si trova qui e non in memoria di massa?

Le RAM sono infinitamente più veloci (in lettura e scrittura) di una memoria di archiviazione e, per quanto detto prima, l’app è un modello e modificarlo significherebbe ricompilare l’applicazione (più tante altre cose che non stiamo qui a spiegare).

Processo e CPU

Ipotizziamo il caso in cui, mentre stai cucinando la tua ricetta (ti ricordo che tu sei la CPU) arriva una chiamata urgente a cui devi obbligatoriamente rispondere.

Allora stacchi i fornelli, metti la pietanza che stai preparando (il processo) in attesa e passi ad un nuovo processo (rispondere) che ha un’importanza più alta. Una volta terminata la chiamata, riprendi la ricetta e ricominci dal punto in cui l’avevi lasciata.

Le CPU, di tipo single core, funzionano esattamente allo stesso modo. Elaborano solamente un processo per volta, passando a quelli a priorità più alta quando necessario.

Questo significa che, se stai utilizzando un’applicazione e devi switchare ad un’altra, l’applicazione messa da parte viene bloccata in quanto la CPU è occupata nello svolgere le operazioni del nuovo processo (cosa che accade, più o meno, su iOS passando da un’app all’altra).

Nella realtà, una CPU moderna come quella dei nostri iPhone, riesce ad elaborare più processi in pseudo-parallelismo passando da un processo all’altro talmente velocemente da non farci notare le esecuzioni degli altri processi.

Esempio: i client delle email hanno dei processi che vivono in background che controllano la ricezione di nuove email, questi vengono eseguiti anche quando stiamo utilizzando un’altra applicazione. La CPU passa dal processo in corso a quello dell’email con una velocità tale da non far notare il blocco sull’esecuzione del processo in corso.

Thread

Se l’applicazione in run l’abbiamo chiamata processo, che si intende tutto la sequenza di codice in esecuzione da parte della CPU, un thread è un processo dentro il processo. 

Un processo ha sempre un singolo thread e, quando si trova in questa forma, thread e processo sono sinonimi. Thread non è da confondere con i processi figli (quelli dell’immagine più sopra) perché questi ultimi hanno una vita separata rispetto al processo principale (per gli addetti ai lavori, hanno un Address Space diverso).

Un thread o più thread, vivono letteralmente all’interno del processo che li ha generati (quindi hanno accesso allo stesso Address Space). 

Perché dovremmo avere processi dentro processi?

Le CPU moderne, come quelle ormai montante sugli smartphone, presentano sempre più di un Core (processori multi core). 

Con una CPU single core, i processi vengono eseguiti uno per volta. Con una CPU multi core (ipotizziamo a due) si da il via al vero e proprio parallelismo permettendo l’esecuzione di più processi contemporaneamente (ogni core esegue il suo processo).

Ok, quindi se in un core c’è la mia applicazione in esecuzione, nell’altro cosa ci sta?

Esattamente. Il secondo core della CPU nella maggior parte delle applicazioni amatoriali viene sfruttato in minima parte.

I thread essendo dei particolari processi, come tali possono essere presi in consegna da un core della CPU indipendentemente dall’esecuzione del processo che li contiene. Quindi se un core sta eseguendo un thread per lo svolgimento di alcune operazioni, il mio processo può far partire in parallelo un altro thread che verrà eseguito nel core libero e che mi permetterà di eseguirà altri blocchi di istruzione.

Solo per correttezza di informazioni, i thread possono essere eseguiti sullo stesso Core “contemporaneamente” tramite l’utilizzo di architetture particolari e di algoritmi di scheduling che ruotano i thread (in base alle loro priorità e politiche di scheduling) talmente velocemente da non far notare il blocco dei thread non più in esecuzione da parte della CPU.

Esempio

La mia applicazione ha un database sul Core Data di 40.000 elementi. Ipotizziamo una lista di comuni. L’app, tra le tante funzionalità, permette di cercare un comune all’interno di questo array.

Al di là delle ottimizzazioni di codice che si potrebbero fare per semplificare il procedimento, vogliamoci male e immaginiamo la ricerca come la meno performate possibile, ovvero quella sequenziale (si esegue un ciclo su tutti gli elementi in maniera lineare dal primo all’ultimo. Algoritmo di ricerca sequenziale o lineare). In soldoni un ciclo che si comporta così:

var comuneDaCercare = "Pi" 
var comuniTrovati: [String] = []

for indice in 0...40000 {
    let comuneIterato = self.listaComuni[i]
    if comuneIterato.hasPrefix(comuneDaCercare) {
          comuniTrovati.append(comuneIterato)
    }
}

Per tutti i comuni che cominciano per “Pi”, il codice prende l’elemento che rispecchia questo pattern e lo piazza in un array di comuniTrovati. Il ciclo passerà in rassegna tutti gli elementi fino all’ultimo in posizione 40.000.

Con l’utilizzo del singolo thread di default (ti ricordo che processo = thread quando non si hanno altri thread all’interno dell’app), l’applicazione si bloccherà fin quando il ciclo non avrà raggiunto l’ultimo elemento.

Nel caso reale, dunque, l’utente scriverà “Pi” e poi non potrà più selezionare gli altri elementi dell’interfaccia, per esempio tornare indietro e via dicendo, perché la CPU è ancora ferma ad eseguire quel ciclo.

E con i Thread?

Grazie ad un thread potresti trasferire l’esecuzione di quel ciclo su un core libero della CPU permettendo al restante codice ed eventi di essere eseguiti ed intercettati:

// esegui il codice su un nuovo thread - PSEUDO CODICE
eseguiInUnThread {
   for indice in 0...40000 {
       let comuneIterato = self.listaComuni[i]
       if comuneIterato.hasPrefix(comuneDaCercare) {
           comuniTrovati.append(comuneIterato)
       }
   }
}

Se hai capito la teoria, la pratica ti risulterà estremamente semplice. Forse!

Grand Central Dispatch

Apple è stata sempre furba, anche negli aspetti della programmazione. Chi proviene dagli altri linguaggi lo sa benissimo: con i thread non si scherza. Perché, se non si sa realmente quello che si sta facendo, sono più i problemi che i benefici.

La grande mela, per questo motivo, ha offuscato tutta quella macchinosità che consiste nella creazione di un thread e relativa gestione (sincronia, accessi a risorse critiche ecc) creando un oggetto di astrazione superiore che ne facilita l’utilizzo

Il Grand Central Dispatch mette a disposizione un insieme di classi la quale caratteristica principale è quella di permettere allo sviluppatore di delimitare porzioni di codice, chiamati blocchi o task, che potranno essere eseguiti in parallelo su un nuovo thread.

Quindi il GCD (Grand Central Dispatch) allontana lo sviluppatore dalla gestione vera e propria dei thread ma ne permette il loro utilizzo.

Dispatch Queues

Sono diversi gli elementi presenti all’interno del Grand Central Dispatch e che ti permettono di utilizzare i thread, una delle classi presenti è la DispatchQueue.

Fonte: Wikipedia

Un blocco di codice, Task, viene preso in consegna dal Grand Central Dispatch che lo inserirà in una coda di task. Il primo Task inserito è quello che verrà eseguito per primo su un thread libero.

Il GCD troverà un thread libero (lo sviluppatore non sa qual è o quanti sono) e gli darà in consegna il task. Il thread eseguirà il task in parallelo all’esecuzione del thread principale dell’applicazione.

Qualora fosse presente un altro task o altri, ed in base a delle specifiche che sceglierà lo sviluppatore, il successivo potrà essere inserito in un nuovo thread libero parallelizzando e velocizzando le varie operazioni della tua applicazione. Altrimenti, la Dispatch Queue eseguirà un singolo task per volta (sempre su un thread differente rispetto al principale). 

Progetto d’esempio

Basta teoria! Cominciamo a mettere le mani in pasta. Crea un nuovo progetto Single View Application iOS. Seleziona il linguaggio Swift e, una volta completati i passaggi, spostati al file ViewController.swift.

Qui crea un semplice metodo someFunction() che richiamerai nel viewDidAppear:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.someFunction()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    // Una funzione che utilizzerà il GCD
    func someFunction() {
    
    }

}

All’interno della funzione someFunction scriveremo il codice per l’utilizzo del GCD e delle Dispatch Queues.

DispatchQueue con linguaggio Swift

let queue = DispatchQueue.init(label: "it.xcoding.nomeCoda")

Una DispatchQueue ha bisogno del solo parametro label per la sua costruzione. La label è un nome che identifica la coda. Questo nome deve essere univoco dato che, all’interno dell’applicazione, potresti aver bisogno di più DispatchQueue. Apple suggerisce di utilizzare la notazione dei Bundle (o reverse DNS notation) anche se puoi chiamarle come preferisci.

La DispatchQueue ti mette a disposizione due metodi:

  1. sync { codice } esegue il codice all’interno delle graffe bloccando il thread in cui viene eseguito il comando sync.
  2. async { codice } il codice viene avviato e poi eseguito in parallelo al restante codice fuori del metodo.

Più facile vedendo un esempio. Ipotizziamo d’avere questi due cicli:

for i in 0...4 {
    print("", i)
}

for y in 0...4 {
    print("", y)
}

Quello che ci aspettiamo è che prima venga eseguito il primo ciclo, stampando 5 volte il robot, e che successivamente venga eseguito il secondo ciclo.

Async

Proviamo ad utilizzare la DispatchQueue inserendo il primo ciclo all’interno di un async invocato su una queue creata appositamente.

let queue = DispatchQueue.init(label: "it.xcoding.queue")

queue.async {
    for i in 0...4 {
        print("", i)
    }
}

for y in 0...4 {
    print("", y)
}

Cos’è successo qui?

Quando viene eseguito il comando queue.async, il Grand Central Dispatch prende il task presente all’interno delle graffe (cioè tutto il codice) e lo invia ad un thread differente rispetto a quello principale ed in cui si trova il resto del codice.

A questo nuovo thread, inoltre, gli viene detto di eseguire il task in asincronia, cioè in parallelo, rispetto all’esecuzione del thread principale. Per questo motivo una volta eseguito il queue.async l’esecuzione non si blocca ma passa alla riga successiva che è rappresenta dal secondo ciclo.

Sulla console dovresti vedere un print delle faccine alternate. Non devi prenderlo per oro colato, in realtà non vengono eseguiti prima uno e poi l’altro (cioè prima un giro di ciclo del primo task e poi del secondo), nella realtà a livello di CPU i due cicli vengono eseguiti contemporaneamente. In console, dato che si scrive riga per riga, i due thread si contendono l’accesso alla console e per questo si alternano le emoji.

Quality of Services

Se durante la preparazione della tua pietanza ti chiama tuo figlio perché si è fatto male, a meno che non vuoi infierire, passi al task con priorità più alta (dare assistenza).

Anche le DispatchQueue ti permettono di definire la priorità d’esecuzione dei suoi task. 

Peppe, cosa significa?

Partiamo dal presupposto che il main thread ha la priorità più alta. Il main thread ti ricordo che è quello gestisce l’interfaccia e quello dove vive il codice a meno di queue create appositamente.

Questo comporta che una queue, come quella creata poc’anzi, non potrà mai competere con il main thread. Cioè il sistema tenderà sempre a dare maggiore importanza, cioè priorità d’esecuzione, al codice del main thread. Quindi se ti sta venendo la pazza idea di buttare tutto il codice dentro una queue, beh, non farlo.

Allora a cosa serve la priorità?

Dentro un’applicazione, generalmente le più complesse, possono esistere più queue. Ovvero puoi creare più oggetti DispatchQueue con label differenti.

Ora ipotizziamo d’avere un’applicazione tipo DropBox dove puoi conservare grossi file, modificarli se sono testuali e così via.

Il tuo utente deve scaricare un grosso file da 1 GB e contemporaneamente caricare delle immagini sul suo archivio. Ha bisogno però che le immagini vengano caricate il prima possibile ma non può neanche bloccare l’esecuzione del main thread perché vuole navigare dentro l’app.

Dato che conosci le queue e sai che puoi creare più di una queue, crei:

  • queueA: per il download del file
  • queueB: per il caricamento delle immagini
let queueA = DispatchQueue.init(label: "it.xcoding.queueA")
let queueB = DispatchQueue.init(label: "it.xcoding.queueB")

queueA.async {
    // download file
}

queueA.async {
    // upload immagini
}

Per poter definire la priorità di una queue devi aggiungere un nuovo attributo chiamato qos, Quality of Services, all’init della DispatchQueue. Il parametro qos o DispatchQoS è un Enum, puoi consultarlo qui, che ti da la possibilità di scegliere tra questi valori che sono ordinati per livello di priorità (il primo ha priorità più alta):

  1. userInteractive: I task all’interno dovrebbero essere quelli che hanno bisogno di un’esecuzione “istantanea”. Puoi paragonare questo qos a quello che rappresenta il main thread. Ovviamente non ti consiglio di utilizzarlo per interagire con la grafica dell’app (anche se non da problemi. Sotto vedrai il modo corretto per farlo)
  2. userInitiated: Leggermente più lento rispetto all’userInteractive. Ci si aspetta che i task all’interno debbano restituire dei risultati quasi istantaneamente. Per esempio, l’apertura e chiusura di un file dopo l’invio del comando dovrebbe utilizzare questa qos
  3. default: Quando non scelto, il qos della DispatchQueue sceglie un valore compreso tra la QoS Utility e la QoS UserInteractive. Non è consigliato l’utilizzo in quanto è il sistema a sceglierne la priorità.
  4. utility: Qui vanno i task che richiedono del tempo per essere portati a termine (download ed import per esempio). Quindi tutte quelle operazioni che richiederebbero qualche minuto. A livello di User Experience è consigliato utilizzare delle barre d’avanzamento o analoghi sistemi per avvisare l’utente sui progressi del task
  5. backgroundLa sincronizzazione di dati, i backup ed in generale operazioni lunghe (dalla decine di minuti alle ore) ed invisibili all’utente dovrebbero utilizzare questo QoS
  6. unspecified: Rappresenta l’assenza di QoS

In generale default ed unspecified non andrebbero utilizzate in quanto ti tolgono quel potere di cui hai bisogno per gestire correttamente le operazioni della tua applicazione. Quindi spazia tra tutte le altre QoS in base al tipo di lavoro che deve andare a svolgere il tuo codice.

Proviamo ad assegnare una priorità alle due DispatchQueue. Per quanto detto, potresti dare una Quality of Service Utility alla queue del download del file ed una QoS Background per l’upload delle immagini (abbiamo ipotizzato un upload di 1 GB, fosse stato della manciata di mb avresti potuto utilizzare anche una Utility).

Per semplicità utilizziamo sempre i due cicli:

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .utility)
let queueB = DispatchQueue.init(label: "it.xcoding.queueB", qos: .background)

queueA.async { 
    for i in 0...10 {
        print("", i)
    }
}

queueB.async {
    for y in 0...4 {
        print("", y)
    }
}

Quality of Services Dispatch Quelle linguaggio swift

Come previsto ed ipotizzato la queueA, che ha priorità utility, viene data più priorità rispetto alla queueB che ha un QoS background. Infatti alla queueB viene dato uno spazio d’esecuzione solamente all’inizio (ciclo zero), poi viene rallentano per dare spazio alla queueA ed infine viene ripreso al termine.

Tutto ciò non sarebbe stato possibile senza l’utilizzo delle Quality of Services e del Grand Central Dispatch con il linguaggio Swift.

Serial e Concurrent

Ti ho mostrato come le DispatchQueue ed i metodi sync ed async ti permettono di parallelizzare le operazioni della tua applicazioni e come l’utilizzo della Quality of Services ti permetta di assegnare una priorità d’esecuzione alle tue queue. 

Ma cosa accade, invece, a più task inseriti all’interno di una queue?

Sembra ovvio, ma lo ribadisco, che la creazione di una queue è un’operazione onerosa per il sistema (overhead). In pratica, certe volte, è più il tempo perso per creare la queue ed i relativi thread che il tempo d’esecuzione del task stesso.

Per questo motivo è consigliato utilizzare un numero ridotto di DispatchQueue alle quali verranno dati in pasto i vari task da eseguire.

Esecuzione in serie

Ipotizziamo d’avere sempre quei due famosissimi cicli e di volerli eseguire in parallelo. Prima ti ho fatto utilizzare due queue, adesso devi ottimizzare il codice e pertanto decidi di crearne una sola.

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .userInitiated)

queueA.async {
    for i in 0...4 {
        print("", i)
    }
}

queueA.async {
    for y in 0...4 {
        print("", y)
    }
}

La DispatchQueue è una coda (un array per capirci) i quali task (il codice dentro gli async o sync) vengono messi uno dietro l’altro in attesa di essere eseguiti.

Di default, quindi, la DispatchQueue eseguirà i task uno per volta. Questa tipologia di architettura della queue viene detta seriale. Cioè i task vengono eseguiti in serie, uno dopo l’altro.

Ma non avevamo messo async?

Vero! Ma ti ricordo che l’async è il comando che va a definire il metodo d’esecuzione del task rispetto agli altri thread o DispatchQueue presenti nell’app e che quindi non interferisce con i task presenti all’interno della coda.

Esecuzione in parallelo

Per poter parallelizzare l’esecuzione dei task all’interno della stessa queue bisogna introdurre un nuovo parametro al momento della costruzione della coda. Questo parametro si chiama attributes.

L’attributes è un enum con due solo valori:

  1. concurrent: Permette l’esecuzione in parallelo dei task all’interno della queue. Non è possibile assegnargli una priorità, quindi verranno eseguiti tutti un po’ per volta.
  2. initiallyInactive: La queue nascerà come inattiva. Quindi non eseguirà i task quando invocherai il metodo async o sync. L’esecuzione comincerà solamente quando verrà invocato il metodo .activate() sulla queue.

concurrent

Cominciamo a vedere il primo, cioè il concurrent:

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .userInitiated, attributes: .concurrent)

queueA.async {
    for i in 0...4 {
        print("", i)
    }
}

queueA.async {
    for y in 0...4 {
        print("", y)
    }
}

Un ottimo sistema per poter parallelizzare i task all’interno della queue. Questi vengono prima inseriti all’interno della coda e poi uno per uno eseguiti in maniera alternata.

InitiallyInactive

Questa opzione è utilissima in tutti quei casi in cui si voglia invocare un blocco di codice asincrono o comunque gestito da una DispatchQueue quando si verifica un determinato evento (un trigger potrebbe essere l’arrivo di una notifica).

Prima di procedere, però, è necessario modificare leggermente il codice.

Per prima cosa la creazione della queue o la definizione della variabile/costante deve essere spostata all’esterno. Quindi la queue deve diventare una proprietà della classe.

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .userInitiated, attributes: .initiallyInactive)

func someFunction() {

    queueA.async {
        for i in 0...4 {
            print("", i)
        }
    }

    queueA.async {
        for y in 0...4 {
            print("", y)
        }
    }
    
    var something = true // il nostro trigger event
    
    if something {
        queueA.activate()
    }
    
}

In questo modo, solo quando la variabile something sarà uguale a true verrà attivata la queue ed i relativi task all’interno. Da notare che i task verranno eseguiti in seriale e non in parallelo.

Concurrent ed initiallyInactive

Posso far partire la queue in differita ed i task in concorrenza tra loro?

Ovviamente si.

Per farlo ti basta inserire .concurrent e .initiallyInactive all’interno di un array che poi passerai al parametro attributes.

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .userInitiated, attributes: [.initiallyInactive, .concurrent])

Esecuzione differita

L’ultimo tassello che completa il primo quadro sul Grand Central Dispatch con il linguaggio Swift è quello che riguarda l’avvio differito di un task. Al momento, infatti, una volta invocati i metodi async e sync, l’esecuzione del codice al loro interno parte “istantaneamente”.

Esiste un metodo chiamato asyncAfter che permette di far partire l’esecuzione del task N tempo dopo l’invocazione del metodo stesso.

Questo è un sistema interessante e veloce che ti permette di eseguire un codice una sola volta ed in differita rispetto ad un evento.

Il metodo classico prevederebbe l’utilizzo dei Timer che, per quanto semplici possano essere, portano con se alcune complicazioni che derivano dalla struttura che li rappresenta a basso livello. Per esempio, un problema dei Timer, è quello che devono essere invalidati quando non più utilizzati onde evitare un abuso di risorse (Qui un resoconto Apple sul perché o non dovresti utilizzare i Timer).  Ad ogni modo i Timer dovrebbero essere utilizzati quando c’è da eseguire un codice che si ripete ciclicamente nel tempo.

Il metodo asyncAfter vuole come parametro un oggetto di tipo DispatchTime che rappresenterà la data inteso come istante in cui dovrà partire il codice inserito. Noi lo costruiremo partendo dal DispatchTimeInterval che puoi benissimo paragonare ad un valore di tipo Double, che sommeremo all’istante di tempo corrente, il quale ci darà il DispatchTime finale. 

let time: DispatchTime = DispatchTime.now() + DispatchTimeInterval.seconds(5) // t + 5 secondi

I case che mette a disposizione il DispatchTimeInterval sono quattro: seconds, milliseconds, microseconds e nanoseconds e ti serviranno per poter definire l’istante di tempo, in una delle quattro grandezze, in cui dovrà avviarsi il task. 

A questo punto ti basterà creare una nuova DispatchQueue ed utilizzare il metodo asyncAfter a cui passerai il time d’esecuzione:

let queueA = DispatchQueue.init(label: "it.xcoding.queueA", qos: .userInitiated)

let time: DispatchTime = DispatchTime.now() + DispatchTimeInterval.seconds(5)

queueA.asyncAfter(deadline: time) { 
    print("dopo 5 secondi dall'esecuzione")
}

Main e Global Queue

Prima di passare ad un esempio reale è necessario che io ti metta a conoscenza dell’esistenza di due DispatchQueue di default. Sono rispettivamente la Main Queue e la Global Queue.

Come già ti avevo accennato l’operazione di creazione di una DispatchQueue ha un costo non indifferente paragonato alla mole di lavoro che andrà a compiere nella maggior parte delle situazioni.

Per questo motivo, il Grand Central Dispatch, mette sempre a disposizione due queue di default a cui poter assegnare i task dell’applicazione.

Le due queue in questione sono gestite da due proprietà della classe DispatchQueue (in realtà la global viene restituita da un metodo della classe). Accedendovi potrai direttamente invocare i metodi sync, async e asyncAfter.

  • DispatchQueue.main ti permette di operare sul main thread dell’applicazione che è anche quello, lo ripeto, che gestisce l’interfaccia grafica dell’applicazione. Task onerosi su questa queue comporteranno il rallentamento o il blocco dell’interfaccia. Può essere utile per ritornare sul main da un’altra queue (tipo la global o le tue personali) che non interagiscono con la main.
  • DispatchQueue.global(qos = .default) è una seconda queue con QoS Default e scollegata dal main thread. Puoi utilizzarla per piccole operazioni asincrone o sincrone che non hanno bisogno di interagire con il main e che non richiedono di una queue apposita con un Quality of Services diverso dal default. 
    • Se necessario, puoi passare un QoS come parametro del metodo per utilizzarla come una queue custom a tutti gli effetti.
DispatchQueue.main.async {
    // lavora sul main thread
}

DispatchQueue.global().async {
    // queue custom con qos default
}

DispatchQueue.global(qos: .utility).async {
    // global con qos
}

DispatchQueue.global().async {
    // eseguito sulla global
    DispatchQueue.main.async {
        // ritorno sul main dalla global
    }
}

Nella maggior parte dei casi finirai per utilizzare una delle due sopra citate. Ma noi siamo developer e ci piace complicarci la vita!

Esempio con URLSession

Per evitare di mettere nuova carne sul fuoco, voglio farti vedere una semplice applicazione di quanto visto sopra. Per semplicità d’apprendimento e per evitare di uscire dal topic di questo tutorial ti mostrerò solamente il codice (do per scontato che tu sappia già sviluppare a questo punto del tutorial, altrimenti esci da questo corpo finché sei in tempo).

Cioè che voglio farti vedere è un semplice download di un file json che contiene una lista degli ultimi film usciti al cinema.

La lista dei film la prenderemo dal servizio The Movie Database a cui se volete potete registrarvi per ottenere la apiKey da utilizzare per le chiamate. Altrimenti utilizza la mia:

func nowPlaying(page: Int = 1) {
    
    let apiKey = "e3ad70a80239a24126bbd592bc17e1fd"
    let url = URL.init(string: "https://api.themoviedb.org/3/movie/now_playing?api_key=\(apiKey)&language=it-IT&page=\(page)&region=it")!


}

Faremo un semplice get sulla prima pagina del metodo now_playing che ci restituirà gli ultimi film (pagina 1) al cinema nel nostro paese.

Con JsonFormatter analizza la struttura del JSON restituito dall’url della funzione e di conseguenza creiamo una struct che ci faciliterà l’operazione di mapping dei valori. Per velocizzare e rimanere concentrati sul GCD i dati che prenderemo saranno solamente:

struct Film {
    var title: String
    var author: String
    var release_date: String
    var overview: String
}

Dunque cosa faremo?

  • Scaricheremo il json in background
  • Mapperemo ogni elemento del json con un oggetto Film
  • Per ogni elemento aggiorneremo l’interfaccia dell’applicazione (ipotizziamo una tabella)

Il download lo eseguiremo con una classe che alle sue spalle fa uso del Grand Central Dispatch e che, al contempo, fornisce alcuni metodi utili nelle connessioni http. Tale classe si chiama URLSession (te ne parlerò nel dettaglio nelle altre puntate).

Il metodo dataTask di un oggetto URLSession, noi utilizzeremo lo shared (cioè il singleton), ha una completionHandler  (cioè una closureche viene attivata quando la funzione ha finito di scaricare il dato associato all’URL a cui si connetterà.

URLSession.shared.dataTask(with: url) { (data, response, error) in
    
}

Quando l’esecuzione arriva alla riga d’avvio del dataTask, questo fa partire una nuova queue nel quale viene inserito il task di download e ricezione del contenuto presente dentro l’url. Quando il task viene completato, in background ed in asincronia rispetto il resto dell’app, viene svegliato il codice presente dentro la completionHandler. 

Ora, se hai capito un po’, il codice all’interno della closure non si trova sul main thread, ma su un thread in background.

L’errore che commettono tutti, quando utilizzano una funzione della URLSession, è pensare che il codice all’interno della callback possa andare ad interagire direttamente con l’interfaccia dell’app.

Quindi all’interno della completion, se volessi aggiornare la View, cosa dovrei fare?

Semplice. Il Grand Central Dispatch ci mette a disposizione la queue main che lavora sul main thread, quindi dovrai utilizzare questa per poter ritornare sull’interfaccia.

URLSession.shared.dataTask(with: url) { (data, response, error) in
    // eseguito su un thread diverso dal main
    
    // Qui eseguo il mapping
    
    DispatchQueue.main.async {
        // ritorno sul main thread ed aggiorno la view
        
        // qui aggiorno la view
    }
}

A questo punto il resto è una semplice trasformazione del data restituito dal JSONSerialization. Passaggio che può benissimo avvenire all’interno della closure, quindi sul back thread, ed infine passare il nuovo dato all’interfaccia da un async invocato sulla main queue.

func nowPlaying(page: Int = 1) {
    
    let apiKey = "e3ad70a80239a24126bbd592bc17e1fd"
    let url = URL.init(string: "https://api.themoviedb.org/3/movie/now_playing?api_key=\(apiKey)&language=it-IT&page=\(page)&region=it")!

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        // eseguito su un thread diverso dal main
        // Qui eseguo il mapping
        self.arrayFilm = self.mapDataToFilms(data)
        
        
        DispatchQueue.main.async {
            // ritorno sul main thread ed aggiorno la view
            self.tableView.reloadData()
        }
    }

}

Lo stesso principio può essere utilizzato per scaricare immagini ed aggiornare una UIImageView. 

Ti faccio notare che la mancanza di tale passaggio avrebbe implicato il non aggiornamento dell’interfaccia. Con questa semplice conoscenza del Grand Central Dispatch niente più sembrerà così strano.

Una piccola miglioria con un grande impatto sull’utilizzo dell’applicazione.

DispatchWorkItem

Spesso capiterà di dover eseguire diverse volte ed in diverse parti dell’applicazione, le stesse identiche operazioni. Questo comporta una ridondanza enorme nella scrittura del codice che, vuoi o non vuoi, diventa un copia incolla costante.

Se poi ci metti nel mezzo il Grand Central Dispatch il codice da ripetere può diventare enorme.

La classe DispatchWorkItem ti permette di incapsulare, in un oggetto, un codice che dovrà essere dato in pasto ad una DispatchQueue o, in alternativa, direttamente al main thread.

func someFunction() {
    
    var value: Int = 0
    
    let dispatchItem = DispatchWorkItem.init {
        value += 5
    }
    
    dispatchItem.perform() // avvia il DispatchWorkItem

    let queue = DispatchQueue.global(qos: .utility)
        
    queue.async(execute: dispatchItem) // esegue il DispatchWorkItem sulla queue in async
        
    queue.async {
        dispatchItem.perform() // analogo al codice precedente, ma con la sintassi classica
    }

}

Così come te lo sto presentando potresti essere indotto a pensare che il vantaggio sta solamente nella semplicità di scrittura. In effetti siamo riusciti a sommare +5 alla variabile value per tre volte, due delle quali da una queue parallela alla main.

Il vero vantaggio viene dalla presenza di alcuni metodi accessori presenti all’interno della classe DispatchWorkItem. Tra questi troviamo:

  • notify: che permette di eseguire un codice su una queue qualsiasi al termine dell’esecuzione del DispatchWorkItem
  • cancelti da la possibilità di cancellare l’esecuzione del DipatchWorkItem. Una volta cancellato non potrà più riprendere quindi dovrai ricrearne uno nuovo

Proviamo a fare un esempio leggermente più complesso per capire meglio l’utilità del DispatchWorkItem con il linguaggio Swift.

Ipotizziamo di voler scaricare 6 immagini da internet in maniera concorrente. Cioè con ogni task eseguito in parallelo. Quindi all’interno di un DispatchWorkItem aggiungerai il codice per il download dell’immagine e con un ciclo reitererai l’operazione in modo da creare 6 DispatchWorkItem. Questi DispatchWorkItem li conserveremo in un array per poi poterli eseguire successivamente.

var workItems = [DispatchWorkItem]()

for i in 1...6 {
    var currentImage = ""
    
    let dispatchItem = DispatchWorkItem.init {
        let randomSleep = arc4random_uniform(2) // 1
        sleep(randomSleep)
        currentImage = "image-\(i).jpg" // ipotizziamo che qui sia avvenuto il download
    }
    
    dispatchItem.notify(queue: DispatchQueue.main, execute: { // 2
        print(i, currentImage)
    })
    
    workItems.append(dispatchItem) // 3
}
  1. Ho inserito uno sleep(randomSleep) solo per simulare il download di immagini con diverso peso (bloccheranno il thread in cui verrà invocato per un valore compreso tra 0 e 2 secondi).
  2. Dopo che ho creato il DispatchItem ho invocato su questo il metodo notify sulla DispatchQueue.main. Il notify verrà attivato solamente quando il DispatchWorkItem avrà concluso la sua esecuzione permettendoci così di aggiornare la View con il dato scaricato.
  3. Infine ho aggiunto il dispatchItem all’array workItems. 

A questo punto ti basta creare una queue con attributo .concurrent:

let queue = DispatchQueue.init(label: "it.xcoding.queue", attributes: .concurrent)

for i in 0...workItems.count-1 {
    queue.async {
        let currentWorkItem = workItems[i]
        
        if i == 2 { currentWorkItem.cancel() } // simuliamo un errore sulla workItem 2 e cancelliamo la sua esecuzione

        if !currentWorkItem.isCancelled {
            currentWorkItem.perform()
        }
    }
}

Con un ciclo vado ad iterare tutti gli elementi dell’array workItems prendendo l’indice. Dentro il ciclo esegue un async sulla queue dove viene eseguito il DispatchWorkItem del ciclo corrente.

Quando il ciclo raggiunge la posizione i == 2 simulo un errore e blocco quel workItem. 

Vediamo cosa succede a livello d’esecuzione:

dispatchworkitem-linguaggio-swift

Dato che si tratta di una queue concurrent i task all’interno, creati dal ciclo, vengono invocati istantaneamente. Una volta finiti i DispatchWorkItem, questi notificano la main queue che provvederà a stampare in console il nome del file scaricato.

Caso particolare è il DispatchWorkItem 3 (cioè indice uguale a 2) che viene arrestato prima della sua esecuzione. Da notare che il metodo notify viene comunque invocato ma, dato che non è stata valorizzata la variabile con il nome del file, viene stampato solo l’indice ed una stringa vuota.

Quindi, in definitiva, quando conviene utilizzare i DispatchWorkItem?

I DispatchWorkItem, rispetto ad un’esecuzione ciclica del metodo async (avremmo potuto ottenere lo stesso risultato con un ciclo di queue.async), permettono di controllare l’esecuzione di ogni singolo task e di organizzare meglio il flusso d’esecuzione di un processo.

Infatti io ho scritto tutto all’interno della stessa funzione per semplificare le spiegazioni. In un progetto reale avrei potuto dividere la fase di definizione dei task rispetto a quella dell’esecuzione.

DispatchWorkGroup

Ipotizziamo d’avere un insieme di operazione asincrone, tipo quelle viste poco fa per il download di N immagini, e di voler eseguire un codice solamente alla fine di tutte le N operazioni.

Il Grand Central Dispatch mette a disposizione un oggetto particolare chiamato Dispatch Work Group che ti permetterà di raggruppare operazioni asincrone all’interno di un singolo oggetto.

Una volta creato un DispatchGroup, potrai inserire i task al suo interno utilizzando il metodo async, invocato su una queue, che ha per parametro group: DispatchGroup:

let workItemA = DispatchWorkItem.init {
    sleep(1)
    print("operation A")
}

let workItemB = DispatchWorkItem.init { 
    sleep(2)
    print("operation B")
}

let dispatchGroup = DispatchGroup.init()

let queue = DispatchQueue.init(label: "it.xcoding.queue")

queue.async(group: dispatchGroup, execute: workItemA)
queue.async(group: dispatchGroup, execute: workItemB)


dispatchGroup.notify(queue: DispatchQueue.main) {
    print("group operation ended")
}

Così facendo il notify verrà invocato solamente quando i due workItem termineranno.

Una cosa importante da dire è che il DispatchGroup può gestire DispatchWorkItem che lavorano su DispatchQueue differenti. Nel frattempo ne approfitto per farti vedere l’utilizzo del DispatchGroup senza l’utilizzo dei DispatchItem e del metodo d’inserimento automatico nel group.

let dispatchGroup = DispatchGroup.init()

let queueA = DispatchQueue.init(label: "it.xcoding.queueA")
let queueB = DispatchQueue.init(label: "it.xcoding.queueB")

dispatchGroup.enter()
queueA.async {
    sleep(1)
    print("dispatch su queueA")
    dispatchGroup.leave()
}

dispatchGroup.enter()
queueB.async {
    sleep(2)
    print("dispatch su queueB")
    dispatchGroup.leave()
}


dispatchGroup.notify(queue: DispatchQueue.main) {
    print("group operation ended")
}

Quando non vuoi utilizzare i DispatchWorkItem, ma vuoi comunque usufruire del DispatchGroup, puoi generare l’accesso manuale al gruppo.

Per capire il funzionamento del codice devi pensare che all’interno DispatchGroup c’è una variabile di tipo Int che di default è 0. Questa tiene il conto di quanti task sta gestendo. Se è 0 vuol dire che non c’è nessun task all’interno.

Quando la variabile aumenta di 1, e aumenta quando entra un DispatchWorkItem o quando si utilizza il metodo enter(), il DispatchGroup non può notificare l’esterno.

Per notificare l’esterno questo valore deve ritornare a zero. Per decrementare il valore si utilizza il metodo leave().

Leave() ed enter() sono di default inseriti quando si utilizza il metodo sync con il passaggio del group. In tutti gli altri casi questo step deve essere svolto manualmente.

Importante è capire che un enter deve essere sempre seguito da un leave perché il DispatchGroup non può trovarsi, a fine esecuzione, con il conteggio maggiore o minore di zero.

Nel codice ho inserito gli enter fuori dal metodo async perché volevo definire prima dell’avvio del task l’incremento del DispatchGroup. Se ci pensi è logico per in genere prima si avvisa dell’ingresso (bussando la porta) e poi successivamente si entra.

Lo stesso vale per l’uscita. Questi sono stati inseriti all’interno del task, ed alla fine, per notificare correttamente il DispatchGroup dell’effettiva uscita solamente a fine task.

Considerazioni

Il Grand Central Dispatch con il linguaggio Swift è uno degli argomenti più complessi da comprendere fino in fondo. Non tanto perché il codice è complesso, in questo Swift ci aiuta parecchio, ma tanto perché entrare nei meccanismi del multithreading e multicore richiede tempo per essere assimilato.

Nella maggior parte dei casi siamo abituati a pensare e ragionare proceduralmente (faccio A, poi B, poi C…). Poche volte proviamo a fare cose contemporaneamente e quando lo facciamo non vanno sempre bene (solo le donne hanno questa abilità).

Le macchine, vuoi o non vuoi, hanno la capacità di eseguire più compiti contemporaneamente e questo se sfruttato a pieno significa ridurre drasticamente tutti i tempi dei processi produttivi.

Quindi, pian piano e senza abusarne, comincia a modernizzare il comportamento del tuo codice sfruttando il parallelismo datoti dal Grand Central Dispatch. Se trovi qualcosa che appesantisce l’app (la rallenta), buttala su una Queue in background senza esitazioni (male che va ripristini il progetto).

Da oggi potrai dare il via alle danze con il Grand Central Dispatch!

Buona Programmazione!

P.S. Alcuni esempi sono una trasposizione di quanto fatto nel tutorial pubblicatodal sito web Appcoda.com che ho trovato ottimi per introdurre i concetti del Grand Central Dispatch con il linguaggio Swift.

Changelog

  • 21/03/2017 – Aggiunto il changelog. Prima versione del tutorial.

Come creare un Framework con Xcode 8. L’arte del riutilizzo e condivisione del codice

come-creare-un-framework-con-xcode

UIKit, MapKit, VisibilitàKit, SoldiKit. Tutto il mondo delle applicazioni gira intorno ai frameworks (tranne l’ultimo citato).

Ma cos’è un framework?

Un framework è un’insieme di classi e strutture dati. Queste classi vengono conservate in uno speciale contenitore, per l’appunto il framework, che ne impedisce la lettura del contenuto (inteso come codice sorgente) e ne permette solo l’utilizzo (chiamate alle funzioni o agli init delle classi abilitate. Vedremo meglio tra poco).

Ok, ma a che servono i framework e perché dovrei crearne uno?

Immagina d’avere un problema con un’applicazione. La tua applicazione, per esempio, non sa cuocere due uova al tegamino. Dopo che ti sei sbattuto (il termine non è un caso) per diversi giorni per trovare una soluzione, alla fine te ne esci con una classe che ti permette di risolverlo:

class Cuoco {
    func uovaAlTegamino() {

    }
}

Così ogni volta in cui ti ritroverai a sbattere due uova, grazie a quella classe, non dovrai far altro che invocare quel determinato metodo fregandotene di tutto quello che c’è all’interno (perché sai che funziona).

Dopo qualche settimana sviluppi una nuova applicazione e toh! eccolo là, il problema delle uova al tegamino ritorna puntualmente. Ma tu sei furbo e ti ricordi che avevi creato una classe nel precedente progetto. Allora torni là, copi la classe e la incolli nel nuovo progetto.

Creando un tuo framework personale metterai definitivamente fine al processo logorroico di copia ed incolla del codice tra i tuoi stessi progetti. Perché, inserendo la classe Cuoco dentro il framework, dovrai semplicemente importare ques’ultimo ogni qual volta avrai bisogno del suddetto framework in un progetto.

In più, qualora un tuo amico developer dovesse avere il tuo stesso problema o semplicemente sei un piccolo San Francesco da Cupertino, puoi condividere un framework assicurandoti sempre che venga utilizzato solo in quel preciso modo che hai definito (cioè senza bruciare le uova) e senza permettergli di vedere il sistema utilizzato (la tecnica per cuocere le uova).

Premessa fatta. Vediamo insieme come creare un framework con Xcode 8!

Il progetto

Creare un framework per iOS è qualcosa di relativamente semplice. Esiste già un Template preconfezionato che ti permette di partire da un progetto bello e pronto. Apri Xcode, crea un nuovo progetto di tipo Cocoa Touch Framework

creare-un-framework-per-ios-con-xcode

Il template porta con se alcuni file, che per il momento, puoi benissimo tralasciare.

progetto-framework-xcode-8

Classi private e public

Qui si vede con leva maggiore l’importanza dell’utilizzo dei modificatori di visibilità spiegati nel corso gratuito sul linguaggio Swift e poi ripresi nel corso iOS.

Dato che il framework potrebbe essere destinato al grande pubblico è importante definire a priori quali debbano essere le classi/metodi/variabili ecc utilizzabili dallo sviluppatore finale.

Una classe marcata come private è accessibile solo dall’interno del modulo di riferimento (cioè solo dall’interno della “cartella” in cui inserirai i file del framework). Una classe marcata come public sarà usufruibile dal progetto che implementerà il framework.

Ad ogni modo, di default, qualsiasi dichiarazione ha il modificatore internal cioè è accessibile solo dall’interno del modulo (quindi se definisci una class come public, i suoi metodi rimarranno comunque internal). Quindi se vorrai renderla public o private dovrai forzarlo con un modificatore d’accesso.

Aggiungi una nuova classe al progetto ed inserisci il seguente codice:

import Foundation

public class XcodingClass {
    
    private var varPrivate: Int?
    public var varPublic: Int?
    
    
    public init() {}
    
    public func funcPublic() {
        print("\(varPrivate)", "\(varPublic)")
    }
    
    private func funcPrivate() {}
    
}

La classe è marcata come public quindi sarà accessibile dal progetto che implementerà il framework. Dall’altro lato, al suo interno, ci sono var/func public e private. Le private non saranno utilizzabili dall’esterno mentre le public si. Ovviamente il public interno poteva essere omesso dato che la classe è di default public. L’ho inserito solamente per capire velocemente questa differenza.

Compilare ed Esportare

Dove trovo il file .framework e come lo importo?

Per creare il file finale, cioè quello con estensione .framework, una volta aggiunte le varie classi ti basta cliccare su Product/Build per compilare il progetto. 

compilare-un-framework-xcode-8

Il file NomeFramework.framework puoi recuperarlo cliccato con il tasto destro sul file omonimo che trovi dentro il progetto e selezionando Show in Finder:

show-in-finder-nuovo-framework-xcode

A questo punto ti basterà prendere il file e trascinarlo nel progetto in questione.

Dal progetto dell’applicazione ti basterà eseguire un normalissimo import nomeFramework nei vari file in cui vorrai utilizzarlo.

Progetto App e Framework insieme

Moltissime volte è utile poter lavorare al progetto del framework e contemporaneamente vedere come funziona all’interno di un progetto reale (per esempio in un’applicazione). Il sistema che ti ho fatto vedere sopra, cioè della compilazione e del trascinamento del framework all’interno del progetto dell’app è noioso e fa perdere un sacco di tempo ogni qual volta devi aggiornare il framework.

A questo punto puoi utilizzare un sistema ingegnoso che ti permette di lavorare contemporaneamente al framework e all’applicazione.

In un progetto qualsiasi di un’applicazione, dal Project Navigator, seleziona la cartella e con il tasto destro seleziona Add file to “Nome progetto”. Spostati nella cartella in cui hai creato il framework ed aggiungi il file con estensione .xcodeproj:

framework-progetto-e-app-progetto-xcode

Alla fine della solfa dovresti avere il progetto del framework all’interno del progetto dell’applicazione:

framework-xcode-e-linguaggio-swift

condividi xcoding

Ho impiegato un po’ di tempo a scrivere questo tutorial. Se ti va, ricambia il mio lavoro, con un like alle mie pagine!

[addtoany]

[mailmunch-form id=”101287″]

Grazie ;)

Link al framework

Da qui come si utilizza il framework?

Per poter utilizzare il framework dal progetto dell’applicazione, spostati nel Project Info sul pannello General. Scendi in fondo alla pagina e, dove trovi Embedded Binaries clicca sul tasto + ed aggiungi il file nomeFramework.framework. Automaticamente dovrebbe comparire il framework anche su Linked Frameworks and Libraries:
come-creare-un-framework-con-xcode-e-linguaggio-swift

Utilizzo

L’utilizzo è analogo all’esportazione del file .framework in maniera normale. Nel caso in cui non ti dovesse far importare il framework, prova a compilare il target Framework. Cioè dal campo vicino al tasto di avvio del progetto (quello a forma di triangolo) seleziona il target NomeFramework ed avvia la compilazione.

Alla fine dovresti poter importare correttamente il framework ed utilizzare tutto ciò che c’è di pubblico lì dentro:

utilizzo-framework-custom-xcode

Considerazioni

Per quanto riguarda la distribuzione del framework con Cocoa Pods o altri sistemi né parlerò in un tutorial tutto dedicato all’argomento.

Una cosa che ti raccomando di fare è quella di pensare al framework come qualcosa di riutilizzabile. Non crearlo se non è riutilizzabile in N progetti. Cioè, se quello che ogni volta farai sarà modificare il framework in base alle esigenze del progetto in cui lo implementerai, vorrà dire che starai sbagliando ad impostarlo.

A tal proposito, se il progetto è da distribuire, il framework dovrebbe fare un uso massiccio dei Generics, ne ho parlato in questo tutorial, onde evitare tutti i problemi legati al casting.

Buona Programmazione!

Changelog

  • 10/04/2016 – Aggiornate alcune parti tecniche.
  • 14/11/2016 – Aggiunto il changelog.

Generics con il linguaggio Swift | Dai flessibilità al tuo codice

generics con il linguaggio swift

Peppe, sei un tipo alquanto generico. Quello che può sembrare un insulto, per gli sviluppatori, è quasi un complimento. Eh si, perché la programmazione generica o, Generics con il linguaggio Swift, è qualcosa che eleva le tue capacità di scrivere e progettare le applicazioni.

Se è la prima volta che ne senti parlare, non fartene una colpa. Chi sa cosa sono i Generics, o crede di saperlo, si muove sempre nell’oscurità. Aspettano il momento buono per uscirsene allo scoperto con questi fantomatici Generics per farti sentire in imbarazzo o quanto meno perplesso.

Ma bando alle ciance, cosa sono i Generics e come si usano con il linguaggio Swift?

Partiamo da un presupposto. Se vuoi diventare un ottimo sviluppatore, non solo di applicazioni iOS, devi essere in grado di riutilizzare il codice che crei. Non solo tra un progetto ed un altro ma anche tra i file del tuo Xcode Project. Quindi, presta attenzione a questo strumento, prima o poi ti ci imbatterai.

Ma andiamo a noi. Per farti capire cosa sono i Generics partiamo da un esempio classico.

Ci sono due variabili all’interno della tua applicazione, b, e ad un certo punto ti serve invertire i loro valori. Cioè deve contenere il valore di il valore di a. In pratica, se a=6 e b=1, alla fine della storia b=6 ed a=4.

Che difficoltà c’è in questo? Nessuna aggiungerei io. Dato che conosci le funzioni del linguaggio Swift, provvederesti subito a creare una funzione di swap analoga alla seguente:

var a = 6
var b = 4

func swapInt(a: inout Int, b: inout Int) {
    let temp = a
    a = b
    b = temp
}

swapInt(a: &a, b: &b)
print("a = \(a)") // a = 4
print("b = \(b)") // b = 6

E se adesso ti chiedessi di provare ad invertire due variabili String?

Aspetta un momento Peppe, ma il codice non è sempre lo stesso con i Tipi di Dato della funzione cambiati? 

Esattamente. Solo che, non potendo più utilizzare la funzione swapInt, creata poc’anzi, saresti costretto a crearne una nuova, con il corpo (cioè il codice) uguale e l’intestazione diversa:

func swapString(a: inout String, b: inout String) {/*codice uguale al precedente*/}

Qui entrano in gioco i Generics.

I Generics permettono di creare funzioni, classi, strutture, enum e quant’altro in con una sintassi universale. Cioè che non dipendono da un Tipo di Dato definito (che sia di sistema o generato dallo sviluppatore).

Comincia a capire l’importanza di questo strumento?

Allora vediamo insieme come usare i Generics con il linguaggio Swift. Si parte!

Capire i Generics

Essendo per definizioni generici, i Generics non hanno una simbologia definita. Cioè non esiste l’equivalente di String/Double ecc per i Generics. Lo dico perché ci cascano tutti.

Esistono, invece, delle regole e delle sintassi particolari che ti permettono di definire un qualcosa come Generics (altri linguaggi di programmazione, tipo Java, esortano ad utilizzare una simbologia ben definitiva anche se non obbligatoria).

La regola è abbastanza semplice.

Dopo il nome della funzione, classe ecc vengono fatte seguire le parentesi angolari <>. Da questo momento in poi, il compilatore, sa che stai per utilizzare i Generics.

Cosa va dentro le parenti angolari?

Dentro le angular brackets <> puoi scrivere qualsiasi cosa. Questo qualcosa che scriverai, farà da placeholder o rimpiazzo, per il Tipo di Dato che andrai ad utilizzare realmente.

Dato che è difficile da spiegare a parole, ti faccio subito un esempio.

Riprendiamo la funzione di swap e proviamo a convertirla in una Generics function. 

Chiamiamo swapTwoElement e subito il nome piazziamo le parentesi angolari. All’interno delle parentesi mettiamo la lettera maiuscola T (diminutivo di Type). A questo punto, invece di utilizzare un tipo reale, per i parametri della funzione, potrai utilizzare quella T messa all’interno delle parentesi angolari.

func swapTwoElement<T>(a: inout T, b: inout T) {
    let temp = a
    a = b
    b = temp
}

Cioè quel T, messo dentro le parentesi angolari, viene identificato solo in questo contesto, come nuovo Tipo di Dato fittizio da poter utilizzare a nostro piacimento.

E l’utilizzo? La cosa fantastica e che, all’occhio dell’utilizzatore, non cambia assolutamente niente se non il dover utilizzare una ed una sola funzione per tutte queste operazioni di swap:

var a = 6; var b = 4
swap(&a, &b)

var peppe = "Giuseppe"
var delia = "Delia"
swap(&peppe, &delia)

Voglio essere logorroico.

Non ho usato T perché me lo ha imposto Steve Jobs dall’alto dei cieli. Al posto di T, dentro le angolari, avrei potevo scrivere qualsiasi cosa. L’importante è capire che è solamente un nome fittizio (chiamato placeholder) per permetterti di lavorare con qualcosa di astratto e non definito.

Cioè potevi scrivere anche:

func laMiaGenerics<CazzNeSo>(a: CazzNeSo, b: CazzNeSo) {}

Questa è la regola principale per capire e comprendere i Generics del linguaggio Swift. Adesso vediamo come applicarli e alcuni casi particolari.

Classi Generics

Anche le classi possono essere definite come tipi Generics.

Immagina di voler creare una classe che gestisce una lista di cose non definite. Possono essere Oggetti, String, Double, non ha importanza. La classe deve poter restituire il primo elemento e l’ultimo elemento della lista che gestisce.

Anche per le classi, dopo il nome, vengono fatte seguire le parentesi angolari contenenti il nome del nuovo tipo generico. Le proprietà ed i metodi potranno utilizzare quel placeholder come nuovo tipo fittizio:

class Lista<Element> {
    var lista: [Element] = []
    
    init(data: [Element]) {
        self.lista = data
    }
    
    func firstElement() -> Element {
        return lista[0]
    }
    
    func lastElement() -> Element {
        return lista[lista.endIndex]
    }
}

Per utilizzarla, in fase di inizializzazione del nuovo oggetto, subito dopo il nome dovrai specificare il Tipo di Dato reale che dovrà utilizzare la Lista, i suoi metodi ed attributi.

Per esempio, se volessi creare una Lista di stringhe, scriverei:

var listaStringhe = Lista<String>(data: ["ciccio", "peppe", "chiara"])
listaStringhe.firstElement()

Potresti anche utilizzarlo come contenitore di altri oggetti.

Se la tua app serve a gestire una fattoria di animali, ti potrebbe essere utile la classe Lista generica per conservarli tutti dentro ad un contenitore:

class Animale {}
class Mucca: Animale {}
class Cavallo: Animale {}

var muccaPippo = Mucca()
var cavalloGino = Cavallo()

var listaAnimaliFattoria = Lista<Animale>(data: [muccaPippo, cavalloGino])

Nota bene che non ho dovuto riscrivere una nuova classe per la gestione della fattoria. Ho semplicemente riutilizzato la classe Lista con un contesto differente.

Gli stessi esempi, senza Generics, avrebbero comportato la scrittura di due classi Lista differenti.

Type Constraint

Esercizio 0. Proviamo a creare una semplice funzione Generics che permetta di confrontare due valori dello stesso tipo. Se i valori sono uguali deve restituire true.

func confronta<T>(a: T, b: T) -> Bool {
    guard a == b else {return false}
    
    return true
}

Il nostro Playground genera un errore inaspettato. Ovvero ci comunica che l’operatore di comparazione == non può essere applicato a due oggetti di tipo T, perché?

Generics e Contraints Type

Questo accade perché, i Generics, per loro natura sono svincolati da qualsiasi tipo di vincolo.

Per dirla in maniera più tecnica, i Generics, non ereditano da nessun Protocol o Classe.

Peppe, cosa vuol dire?

I Tipi di Dato del linguaggio Swift, come le String, Double ecc sono delle Struct che ereditano alcuni comportamenti da diversi protocolli. I protocolli, ti ricordo, obbligano una Classe/Struct ad implementare dei metodi.

Nel particolare, i Tipi di default, implementano un protocollo chiamato Equatable. Il protocollo Equatable permette di determinare il comportamento dell’operatore == per quel particolare Tipo.

In base a questa semplice considerazione possiamo dire con certezza che il nostro tipo generico T non eredita dal protocollo Equatable.

Voglio arrivare a dirti che, i Generics, possono essere specializzati facendogli ereditare delle caratteristiche dalle Classi o Protocolli. Quest’operazione prende il nome di Type Constraint (costringere un tipo a comportarsi in un determinato modo).

Puoi aggiungere un Constraint ad un tipo Generics utilizzando questa sintassi:

func generica<T: Protocol, Z: Class>()
class Generica<T: Protocol> {}

Se adesso costringiamo la funzione confronta ad utilizzare il protocollo Equatable, riusciamo facilmente a risolvere l’errore:

func confronta<T: Equatable>(a: T, b: T) -> Bool {
    guard a == b else {return false}
    
    return true
}

confronta(a: "Giuseppe", b: "Delia") // false
confronta(a: 2, b: 2) // true

Esercizio 1. Vogliamo creare una funzione generica che permetta di contare, all’interno di un Array qualsiasi, la ricorrenza di un determinato valore. Per esempio, se hai il seguente array [4,67,4,7,2] ed il valore da cercare è 4, la funzione deve stampare “ci sono due 4 nell’array“.
Ovviamente la funzione deve essere riutilizzabile in altri contesti, quindi anche con array di String, Oggetti ecc.

func countOccurrences <T: Comparable> (array: [T], element: T) -> Int {
    var n = 0
    for x in array {
        if x == element {n += 1}
    }
    
    return n
}

Se sei alle prime armi con il linguaggio Swift ti puoi fermare anche qui con i Generics. Il prossimo paragrafo l’ho pensato per chi ha già un po’ le mani in pasta con lo sviluppo delle applicazioni.

Protocolli e Associated Type

La regola vale anche per i protocolli. Anch’essi possono essere definiti come Generics. Ma cosa succederebbe se i metodi del protocollo avessero bisogno di gestire oggetti che implementano quel particolare protocollo?

Per queste particolare situazioni, si può creare un protocollo di tipo Generics, utilizzando la parola chiave associatedtype seguita dal nome di un Generic Type per riferirti a qualsiasi tipologia d’oggetto non ancora specificato.

protocol SomeProtocol {
    associatedtype GenericType
}
// Questa sintassi è l'equivalente di protocol SomeProtocol<GenricType> N.B. Che non puoi utilizzare nei protocolli

Vediamo un esempio.

Immaginiamo d’avere un protocollo che definisca i metodi di login ad una piattaforma (Firebase, il tuo Oauth sys ecc).

Il protocollo lo chiamiamo Loggable. Al suo interno, definiremo una funzione login con un completionHandler (una closure) che conterrà l’istanza del LoggableObject (il GenericType che conterrà i valori de login corretto) ed un ErrorType per la gestione dell’errore (un altro generic enum definito sotto):

protocol Loggable {
    associatedtype LoggableObject
    func login(withCompletion: (LoggableObject?, ErrorType<String>?) -> ())
}

enum ErrorType<T> {
    case failLogin(T) // T rappresenterà il numero d'errore, la stringa d'errore ecc. 
}

Adesso crea una classe Utente che implementa questo protocollo. Nella fase di implementazione dei metodi definiti dal protocol Loggable potrai aggiungere, al primo parametro della withCompletion, il tipo della classe in cui si trova il metodo:

class Utente: Loggable {
    
    func login(withCompletion: (Utente?, ErrorType<String>?) -> ()) {
        
    }

}

Interessante, no?

[su_spoiler title=”Esempio protocol Loggable applicato” style=”fancy”]

class Utente: Loggable {
    var nome: String!
    
    func login(withCompletion: (Utente?, ErrorType<String>?) -> ()) {
        /* qualche altra funzione per il recupero dell'utente */
        let utenteLoggato: Utente? = self // cambia a nil per simulare il login non avvenuto
        
        guard utenteLoggato != nil else {
            withCompletion(nil, .failLogin("problema nel login"))
            return
        }
        
        /* recupero i dati dell'utente e li assegno a self */
        
        utenteLoggato!.nome = "Giuseppe"
        withCompletion(utenteLoggato!, nil)
    }

    
}

var utenteA = Utente()

utenteA.login { (utente, error) in
    if error != nil {
        print(error!)
        return
    }
    
    print("Benvenuto", utente!.nome)
}

[/su_spoiler]

[su_spoiler title=”Esempio di un protocol Generics per la gestione di una lista applicando ad una classe generica per la descrizione della lista” style=”fancy”]

import UIKit

protocol List {
    associatedtype Item
    
    func insert(newElement: Item)
}

class SomeList<T> : List {
    var list: [T]
    
    init() {
        self.list = []
    }
    
    func insert(newElement: T) {
        self.list.append(newElement)
    }
}

class Cane {}

var rex = Cane()

var listaCani = SomeList<Cane>()
listaCani.insert(newElement: rex)

[/su_spoiler]

Con questo mi fermo qui come primo approccio ai Generics con il linguaggio Swift. Voglio comunque elencarti alcune motivazioni che dovrebbero spingerti ad utilizzarli.

Considerazioni

Se il tuo obiettivo è quello di scrivere un paio di applicazioni, allora no, i Generics non fanno per te. I Generics nascono per la riusabilità e la condivisione del codice. La maggior parte dei framework di terze parti, come quelli standard, utilizzano i Generics proprio per lasciarti piena autonomia d’utilizzo.

Quindi se stavi cercando un sistema per aumentare la tua capacità di scrivere, riutilizzare, astrarre e condividere il codice, allora i Generics fanno proprio al caso tuo.

Non ho trattato tutti gli argomenti che concernono il mondo dei Generics. Essendo dei concetti astratti avrai bisogno di una certa quantità di tempo per poterli sentire pienamente tuoi. Ecco perché non ho voluto sovraccaricare questo tutorial.

Sicuramente scriverò un altro articolo in cui ti farò vedere qualche utilizzo applicato dei Generics con il linguaggio Swift.

Se è la prima volta che approdi sul mio sito, dai un’occhiata a:

Buona Programmazione!

Come creare Sticker Pack per iMessage senza saper programmare!

come-creare-sticker-pack-per-imessage-ios-con-xcode

Ti ricordi come chattavamo qualche anno fa?

Facevamo più o meno così: Caio: “Come va? lol“, Sempronio: “tutto bene XD“. Non pensare di non rientrare anche tu nella categoria. Perché, mi giocherei un polmone, almeno una volta l’avrai fatto anche tu.

Ad ogni messaggio, che ho volutamente scritto senza l’omissione delle vocali (cosa ancor più grave di usare i vari acronimi), facevamo seguire quelle belle faccine create a suon di lettere. Il motivo per cui lo facevi o facevamo era semplice. Internet è sinonimo di velocità e scrivere in maniera abbreviata accelerava le mille mila comunicazioni che intrattenevamo online.

Adesso invece?

esempio-come-creare-sticker-pack-per-imessage-ios
L’App finale del tutorial!

Si vedono Sticker da tutte le parti. Telegram, Facebook Messenger e altre applicazioni simili hanno introdotti gli Sticker come sostituto e complemento delle Emoji. Apple, con la sua app iMessage (la più usata per chattare dagli utenti iOS), non è rimasta a guardare e, finalmente, con il WWDC 2016 ha portato una bella ventata di aria fresca alla sua app di messaggistica.

Il MessageKit, cioè il framework che ti permetterà di creare Sticker Pack e App per iMessage, è entrato a far parte dei must have di tutti gli sviluppatori.

Arrivati a questo punto, la domanda è: Come creare Sticker Pack per iMessage?

Se te lo stai chiedendo, non serve essere uno sviluppatore iOS. Quindi non ti servirà conoscere il linguaggio Swift (anche se te lo consiglio) ma solamente qualche piccolo accenno su come si utilizza Xcode 8 (tratterò tutto nel tutorial).

La mia premessa è stata fatta. Sei pronto a creare Sticker Pack per iMessage? e magari a venderne qualcuno?
Allora cominciamo ad invadere l’App Store!

Creare un progetto Sticker Pack con Xcode

Hai già scaricato Xcode 8? Se non lo hai ancora fatto lo trovi nell’App Store del tuo Mac o se clicchi qui.

Aprilo. Se tutto è andato per il verso giusto, dovresti vedere una schermata in cui ti viene chiesto la tipologia di progetto da cui partire. Seleziona Create a new Xcode project e successivamente clicca su Sticker Pack Application.

Dai un Product Name, cioè un nome allo Sticker Pack, e clicca su Next (gli altri campi li spiego subito sotto l’immagine). Ti verrà chiesto di selezionare la cartella dove inserire il progetto, io li conservo in una cartella su Documenti (o iCloud/DropBox) o nel Desktop come nel caso dei tutorial. Infine premi su Create:

creare-uno-sticker-pack-per-imessage

  • Product Name: Il nome dell’applicazione o, come in questo caso, dello Sticker Pack che andrai a caricare sull’App Store dedicato ad iMessage.
  • Team: Sei hai già un account da sviluppatore, condizione necessaria per caricare App e Sticker sull’App Store, vedrai automaticamente comparire il tuo ID Developer (se ne hai più di uno lo potrai selezionare dal menu a tendina). Se non hai l’account sviluppatore puoi comunque proseguire il tutorial e crearlo una volta che sei sicuro di aver completato tutti i passaggi.
  • Organization Name: É il nome dello sviluppatore o azienda che sviluppa il progetto. Per intenderci, quando vai nell’App Store, questo è il nome che comparirà sotto il nome dell’applicazione stessa.
  • Organization Identifier: É un identificativo univoco che ti rappresenta o che rappresenta la tua azienda. Se vuoi approfondire ne ho parlato nel corso di sviluppo app iOS.
  • Bundle Identifier: Come il precedente, questo identifica univocamente la tua applicazione o Sticker Pack.

Nel caso tu non possegga un account da sviluppatore, che puoi creare qui, non preoccuparti perché potrai comunque testare lo Sticker Pack sul tuo dispositivo iOS e pubblicarlo successivamente.

Proprio perché Apple vuole cercare di raggiungere e sbaragliare i concorrenti, potrai creare Sticker Pack per iMessage senza, davvero, conoscere un briciolo di programmazione.

Quindi fatti forza e non farti intimorire dalle migliaia di schermate di Xcode. Poi se ti fa piacere imparare, puoi sempre seguire i miei corsi o i tutorial che trovi sul blog.

Come aggiungere le immagini per lo Sticker Pack

C’è solamente un file rilevante necessario a creare Sticker Pack per iMessage. Si chiama Stickers.xcstickers ed è il contenitore delle immagini che conterrà il tuo Sticker Pack.

Se lo selezioni, al suo interno, vedrai altre due sotto cartelle:

  • iMessage App Icon: Qui inserirai l’icona del tuo Sticker Pack. Come puoi notare, ci sono tanti piccoli contenitori che rappresentano l’icona nelle diverse possibili visualizzazioni e dimensioni (per esempio, da iPhone ad iPad le dimensioni delle icone cambiano).
    • Per evitare di impazzire, esiste un’applicazione online (la travi anche sullo store del mac) che si chiama Make App Icon che ti permette di creare tutte le immagini partendo da un’immagine 1536×1536 pixel.
    • Se invece vuoi approfondire la differenza tra le dimensioni (Pixel e Point) dai un’occhiata al Corso di sviluppo App o ai tutorial presenti sul sito.
  • Sticker Pack: Qui è dove realmente dovrai caricare le immagini che compariranno su iMessage (sotto ti spiegerò dimensioni ecc).

creare-le-immagini-per-sticker-pack-imessage-xcode

Le immagini che dovrai inserire dentro questa cartella devono essere, preferibilmente, nel formato PNG (cioè con possibilità di trasparenza nello sfondo). Puoi utilizzare anche il formato JPG, APNG e anche il formato GIF (per le animazioni). La dimensione massima di una singola immagine non deve superare i 500 kb.

Se ti va, ho preparato uno Sticker Pack contente le immagini che vedi sotto. Se vuoi, puoi scaricare le immagini in fondo al tutorial (all’interno del box che trovi alla fine della guida, trovi anche il progetto Xcode completo).

Una volta scaricato lo Sticker Pack o, dopo aver creato le tue immagini, trascinale all’interno della cartella Sticker Pack del tuo progetto Xcode:

creare-ed-inserire-immagini-in-uno-sticker-pack-ios

Nel pannello che trovi sulla destra, precisamente nell’Attributes Inspector, potrai modificare la dimensione delle immagini per come appariranno nel preview di iMessage (non verrà modificata la dimensione delle immagini originali). Di default la dimensione classica è Medium. Ci sono anche Small e Large.

Una volta che ti farò vedere come testare gli Sticker, fai una prova modificando questi valori:

modificare-dimensione-di-tutti-gli-sticker-imessage

condividi xcoding

Ho impiegato un po’ di tempo a scrivere questo tutorial. Se ti va, ricambia il mio lavoro, con un like alle mie pagine!

[addtoany]

[mailmunch-form id=”101287″]

Grazie ;)

Testare gli Sticker Pack con il simulatore iOS

Ormai è dalla versione 7, di Xcode, che non è più necessario possedere un iPhone reale per testare le applicazioni o gli Sticker Pack. Puoi tranquillamente utilizzare il simulatore di iOS che si trova all’interno dello stesso Xcode.

In alto sulla sinistra c’è un piccolo bottone a forma di triangolo. Se lo clicchi avvierai tutto il processo di Build & Run del progetto con la conseguente apertura del simulatore. Puoi anche cambiare la versione del simulatore e del dispositivo usato come base (guarda l’animazione sotto).

Una volta avviato, comparirà una finestra dove comparirà, automaticamente, l’app iMessage con il pannello degli Sticker già aperto. Ti basterà selezionare lo Sticker preferito ed inviarlo al contatto di prova presente sul simulatore.

Se hai un iPhone, selezionalo nella lista dei dispositivi dopo averlo prima collegato al Mac, ed infine esegui gli stessi passaggi spiegati sopra:

testare-sticker-pack-su-simulatore-iphone-xcode

Pubblicazione App Store

Pubblicare un Sticker Pack per iMessage si analogo alla pubblicazione di un’app sull’App Store.

Se vuoi pubblicare il tuo Pack, segui questi due tutorial:

  1. Come creare un account da Sviluppatore
  2. Come pubblicare un’app sull’App Store

Considerazioni

Come ti dicevo, creare Sticker Pack per iMessage è un’operazione davvero semplicissima. Quello che posso consigliarti è provare ad inserire delle GIF o altre tipologie d’immagine.

Non c’è, in nessun modo, la possibilità di creare Sticker intelligenti: che si auto selezionano o che si modificano in base al testo scritto dagli utenti. Apple tiene moltissimo alla privacy degli utenti, quindi le uniche cose che potrai fare con gli Sticker sono quelle viste in questa guida.

Discorso a parte sono le applicazioni per iMessage, lì entrano in gioco logiche di sviluppo che spero di prendere in esame al più presto. Ad ogni modo, se vuoi intraprendere la via del soldato Swift non puoi che partire dai corsi o dai tutorial che trovi qui su xcoding.

Nei prossimi tutorial ti farò vedere come caricare un’applicazione sull’App Store (il procedimento è lo stesso per gli Sticker Pack). Quindi rimani sintonizzato.

Qui sotto invece trovi il download del progetto completo e degli Sticker Pack utilizzati (la fonte da cui li ho scaricati è Pixeden.com):

[sociallocker]DropBox Download[/sociallocker]

Buona Programmazione!

Le novità di Xcode 8 e Swift 3. Semplicità, la nuova parola d’ordine!

le novità di xcode 8 e swift 3

Semplicità. Se posso riassumere, con una parola, tutte le novità portate da Apple al WWDC 2016 allora quella parola è semplicità.

Quest’anno Apple ha lavorato pesante per rendere la vita più semplice a tutti gli sviluppatori. La nuova versione del linguaggio Swift, la 3.0, porta con se delle migliorie che renderanno i vostri codici più semplici, leggibili e meno frustanti da debuggare.

Se da un lato Swift 3.0 migliora ciò che sta sotto l’applicazione, dall’altro lato Xcode 8.0 rende tutte queste migliorie più semplici da attuare ed utilizzare.

Quindi, bando la ciance, vediamo insieme le novità di Xcode 8.0 e Swift 3.0!

Indice paragrafi

Swift 3.0! parola d’ordine, Semplicità

Vuoi un esempio lampante?

// swift 2.2
let stringa = "Ciao Xcoders"
stringa.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())

Il codice, scritto con il linguaggio swift 2.2, rimuove dalla costante stringa lo spazio bianco o una nuova linea.  Come lo fa? utilizzando il metodo, della Struct String, chiamato stringByTrimmingCharactersInSet.

Con swift 3.0 tutta quella complessità, data soprattutto dal nome del metodo così lunga, viene convertita in:

// swift 3.0
let stringa = "ciao xcoders"
stringa.trimmingCharacters(in: .whitespacesAndNewlines)

Perché non l’hanno fatto prima?

Con la versione del linguaggio Swift 3.0 gli ingegneri Apple hanno ben pensato di ridurre la lunghezza dei metodi a favorire della leggibilità e comprensione delle funzioni stesse. Funzioni che, lo dico senza vergogna, venivano scartate a priori perché semplicemente troppo complesse da interpretare.

Nel dettaglio quello che hanno modificato è stato spostare un pezzo del nome del metodo/funzione come label dei parametri della funzione stessa.

Che cosaa?

Prendiamo in esame un altro esempio.

// swift 2.2
let xcoding = "1,2,3,4,5,6"
xcoding.componentsSeparatedByString(",")

Nell’esempio scritto in linguaggio Swift 2.2, la funzione componentSeparatedByString rimuove la la stringa “,” dalla costante xcoding e restituisce un array dove ogni posizione è occupata dall’elemento diviso dal metodo (fai una prova sul playground se non conosci questa funzione).

In Swift 3.0 il metodo è stato così modificato:

xcoding.components(separatedBy: ",")

Cioè quella parte del metodo, separatedBy, è stata spostata all’interno della definizione dei parametri della funzione.

Ma le modifiche non riguardano solo e solamente le strutture o classi proprie del linguaggio Swift. Tali modifiche sono state apportate anche ai metodi/funzioni e classi dei vari framework di iOS, macOS, watchOS e tvOS.

Oggi è la giornata degli esempi.

Uno dei metodi modificati della classe UIViewController, anche se quasi tutti sono stati ritoccati, è quello che permette di rimuovere una UIView o un altro UIViewController aperto mediante un segue o uno show.

In swift 2.2 si utilizza il dismissViewController:

// swift 2.2
dismissViewControllerAnimated(true, completion: nil)

In swift 3 un più simpatico ed intuitivo dismiss:

dismiss(animated: true, completion: nil)

Da questi semplici esempi puoi già notare come Apple abbia eseguito un’ottima spending review. Infatti, se trasferisci tutte queste modifiche ad un codice di 500 o più righe, ti rendi subito conto come l’estensione cambi in maniera drastica.

CGAffineTransformIdentity // swift 2.2
CGAffineTransform.identity // swift 3.0

CGAffineTransformMakeScale(2, 2) // swift 2.2
CGAffineTransform(scaleX: 2, y: 2) // swift 3.0

CGAffineTransformMakeTranslation(128, 128) // swift 2.2
CGAffineTransform(translationX: 128, y: 128) // swift 3.0

CGAffineTransformMakeRotation(CGFloat(M_PI)) // swift 2.2
CGAffineTransform(rotationAngle: CGFloat(M_PI)) // swift 3.0

Non sempre le modifiche adottate portano ad una riduzione del testo scritto. Ma questo non deve spaventarti.

Nell’esempio che vedi sopra, nelle funzioni del linguaggio C di trasformazione grafica di un elemento (ne ho parlato dettagliatamente nel mio corso sullo sviluppo di app per iOS) hanno semplicemente spostato un pezzo del nome all’interno dei parametri. In questo modo, è vero che sono leggermente più lunghe, ma hanno sicuramente reso più comprensibile il contenuto dei parametri.

Da UpperCamelCase in lowerCamelCase per Enum e Attributi

Uno dei concetti che più ribadisco all’interno del corso gratuito sul linguaggio Swift è quello di utilizzare le convenzioni note della programmazione generale per uniformare il codice agli standard più comuni.

Tra questi standard c’è quello di chiamare le Classi, Strutture ed Enumeratori con la prima lettera Maiuscola (UpperCamelCase). Questo permette di identificare immediatamente l’appartenenza di un oggetto ad una particolare tipologia di struttura sintattica.

Dall’altro lato, i nomi dei metodi, proprietà, funzioni e parametri dovrebbero essere scritte utilizzando la lettera minuscola (lowerCamelCase).

In Swift 2.2 e versioni precedenti, questa regola non veniva per niente rispettata.

UIInterfaceOrientationMask.Portrait // swift 2.2
UIInterfaceOrientationMask.portrait // swift 3.0

NSTextAlignment.Left // swift 2.2
NSTextAlignment.left // swift 3.0

SKBlendMode.Replace // swift 2.2
SKBlendMode.replace // swift 3.0

Non cambia assolutamente niente a livello di funzionamento ed esecuzione del codice, però, capisci bene che se spacci Swift 3.0 per snello e semplice, allora non puoi non modificare anche queste piccole cose.

L’importanza della grammatica

La grammatica come la matematica è qualcosa che non possiamo abbandonare una volta finita la scuola o l’università. Purtroppo!

Con il linguaggio Swift 3.0, Apple ha voluto marcare l’importanza di utilizzare un metodo scritto utilizzando un tempo al passato semplice (esempio: sorted) rispetto ad uno scritto al presente (esempio: sort).

Le guide ufficiali di Swift, Swift API guidelines, riportano le seguenti diciture:

  • Quando l’operazione è descritta in maniera natura dal un vergo, allora:
    • Usa il verbo al tempo imperativo per un metodo che modifica direttamente la proprietà
    • Applica al metodo il suffisso “ed” o “ing” per un metodo che non muta direttamente la proprietà
  • Preferisci il participio passato per descrivere il nome di una funzione che non modifica direttamente la proprietà
  • Quando l’operazione è descritta da un sostantivo, utilizza il sostantivo per il metodo che non modifica la proprietà e applica il prefisso “form” al metodo che modifica direttamente

Ti assicuro che è più semplice da capire guardando un altro esempio.

Gli array di Int, DoubleFloat String del linguaggio swift 2.2 potevano essere ordinati facilmente utilizzando il metodo sort. In particolare esistono due versioni di questo metodo: sort() e sortInPlace().

  • sort() restituisce un nuovo array ordinato, quindi non modifica l’array originale su cui è stato invocato.
  • sortInPlace() ordina direttamente l’array.

Mentre, con swift 3.0 ed in base alle nuove line guida, i metodi diventano così:

  • sort() è l’equivalente del vecchio sortInPlace(), cioè ordina e modifica direttamente l’array su cui è stato invocato il metodo.
  • sorted() è il vecchio sort(), cioè viene creato e restituito un nuovo array ordinato non modificando l’originale.

Sono modifiche che, per chi viene dalla versione precedente possono portare davvero in confusione.

Come regola empirica, quando viene aggiunto il suffisso “ed” vuol dire che il metodo non modifica direttamente l’elemento ma ne crea uno nuovo lasciando invariato l’invocante. 

Ad ogni modo non preoccuparti parecchio delle modifiche alla sintassi. Xcode 8.0 porta con se un ottimo strumento per convertire i vecchi codici in swift 3.

Le altre modifiche al linguaggio vengono discusse e rese come standard all’interno del corso gratuito sul linguaggio Swift. Vagli a dare un’occhiata ;)

La novità di Xcode 8

Anche Xcode 8 tende a semplificare notevolmente alcuni aspetti della vita di noi poveri sviluppatori. Questa volta non c’è che essere fieri delle modifiche apportate.

Solo per citarne alcune: la documentazione, una nuova gestione delle Size Class, un nuovo metodo per gestire i Certificati (EVVAI!), un tool per la gestione della memoria ecc. E ce n’è talmente tante che difficilmente riuscirai a vederle ed utilizzarle tutte.

Quindi, procediamo con ordine e diamo uno sguardo alle novità maggiori di Xcode 8!

Nuova Documentazione

Partiamo da qualcosa che ti renderà sicuramente più felice. La documentazione.

Parecchie volte mi contattano persone disperate perché non riescono a trovare “le cose” all’interno della documentazione Apple. Non li biasimo, è davvero confusionaria. Talmente confusionaria che Apple ha deciso di cambiargli veste e contenuto.

Con Xcode 8 arriva, finalmente, una nuova documentazione.

nuova-documentazione-xcode-8

Nuovo Font

Non c’è molto d’aggiungere. Nel Text Editor e più in generale con le nuove versioni di iOS, macOS, tvOS e watchOS, hanno cambiato il font nel nuovo San Francisco.

Nuovo Font San Francisco Xcode 8

In più, ma solo per chi c’ha mai fatto caso, è stata inserita la Code Line Highlighted. Ovvero, la linea in cui risiede il cursore del mouse è illuminata (nello screen la linea 19 è dove ho lasciato il cursore del mouse).

Auto completamente testo, selezione colore ed immagini

Quant’è brutto dover aggiungere un colore custom, da codice, ad un elemento? Oppure, quanto scassa la banana dover switchare dalla pagina del codice all’xcassets perché non ti ricordi il nome dell’immagine che vuoi assegnare ad una UIImageView?

Ed ecco che Xcode 8 pone fine ad ogni dramma.

Adesso, quando dovrai assegnare un nuovo colore ad un elemento, ti basterà digitare la parola Color Literal per attivare il Picker dei colori che lo genererà e lo passerà automaticamente con un sistema estremamente simpatico.

Mentre per quanto riguarda le immagini, dovrai scrivere il nome dell’immagine che vuoi utilizzare. Vedrai comparire una lista delle immagini che hai in locale con quel nome. La selezione creerà un oggetto UIImage con l’immagine in questione.

completamento-automatico-dei-colori-ed-immagini-xcode-8

Size Class

Le Size Class sono il sistema che utilizza Xcode per differenziare e modificare i vincoli di Auto Layout nelle varie dimensioni dei dispositivi.

Prima di Xcode 8, si modificava la Size Class utilizzando un bottoncino che si trovava ai piedi dello Storyboard. Di default, chi sceglieva di creare un’app Universal, si ritrovava come primo ViewController un quadrato dalle dimensioni di 600×600 Point. In poche parole, un casino.

Con Xcode 8 è stato rivoluzionato l’intero processo. Adesso non esiste più l’Universal Storyboard. Al suo posto trovi un ViewController con le dimensioni reali (espresse in Point) di uno dei dispositivi Apple.

Le Size Class continuano ad esistere con l’unica differenza che adesso vengono differenziate dalla dimensioni dei vari dispositivi.

nuova visualizzazione size class xcode 8

Al variare di una di queste Size Class cambieranno automaticamente tutte le dimensioni dei ViewController presenti nello Storyboard. In questo modo potrai vedere velocemente quali vincoli funzionano e quali vincoli no, permettendoti di modificarli in quella specifica Size Class in maniera ancora più veloce.

cambio-size-class-xcode-8

L’applicazione che vedi negli screen è l’app finale del corso di sviluppo applicazioni per iOS. Mentre per quanto riguarda l’Auto Layout e Size Class, presto aggiorneremo tutti i tutorial alle nuove versioni di xcode e swift.

Modifica elementi da qualsiasi fattore di Zoom

Una piccola feature che ti aiuterà nell’utilizzo dello storyboard ancora più semplice è lo Zoom scalabile a qualsiasi percentuale. Oltre a permetterti di scalare la percentuale di Zoom da circa 6% a 175% potrai inserire e modificare gli elementi da qualsiasi fattore di Zoom. Evitando così il fastidio di dover selezionare, zoomare nel VC ed infine modificare l’elemento.

Memory Graph

Un nuovo strumento è stato aggiunto ai tool di Debug. Si chiama Memory Graph e ti permetterà di navigare all’interno della memoria dell’applicazione.

Come lo fa? In maniera semplice ed intuitiva, trasformando i blocchi di memoria assegnati ad un particolare oggetto in Nodi. Ogni nodo poi sarà collegato ai suoi parenti stretti.

memory graph xcode 8

A cosa potrà servire realmente ancora non ho avuto modo di scoprirlo. Resta il fatto che è uno strumento interessante ed intrigante.

Certificati automatici

Non c’è cosa più frustrante di dover creare i certificati per la pubblicazione dell’applicazione, le notifiche e chi più ne ha più ne metta. Un vero incubo.

Con Xcode 8 tutto il procedimento è stato nettamente automatizzato. Adesso ti basterà andare nelle impostazioni generali dell’applicazione e cliccare sulla spunta Automatically manage signin per lasciare ad Xcode il compito di creare tutti certificati, i provisioning profiles, e l’identifier dell’applicazione.

creazione-automatica-certificati-xcode-8

Conclusione

Non c’è neanche bisogno che lo dica. Questa è solo un’infinitesima parte di ciò che sono le novità di Xcode 8 e Swift 3. Io ti ho voluto parlare delle maggiori o comunque delle più intuitive e semplici da utilizzare o apprezzare.

Il resto delle novità verrà man mano spiegato nei vari tutorial e corsi del sito man mano che verranno aggiornati alle nuove sintassi e specifiche.

Alla prossima e buona programmazione!

Autenticazione con Firebase e linguaggio Swift | Classica e con Social

Autenticazione con Firebase e linguaggio Swift

Se già ti sei innamorato di Firebase e del suo Database Realtime, sono più che sicuro che apprezzerai la bellezza e semplicità della gestione dell’autenticazione con Firebase e linguaggio Swift (Social e classica).

L’autenticazione dei tuoi utenti ti permetterà di poter gestire meglio i dati che ognuno di essi creerà. Non devi vederlo solamente come il sistema che permette di eseguire il login nell’applicazione, bensì come qualcosa di più esteso e articolato.

Partiamo dal presupposto che l’autenticazione degli utenti non è ben vista dal grande pubblico. Il perché è abbastanza semplice. Fa perdere tempo a chi di tempo non ne ha.

Quindi, perché è importante e quand’è necessario utilizzare l’autenticazione con Firebase e linguaggio Swift?

  1. Uno dei motivi te l’ho spiegato poc’anzi. Utilizzando le credenziali dell’utente, come l’username univoco o l’email, riesci ad archiviare meglio le informazioni generate da un utente. Per esempio, se un utente vuole conservare sul cloud la propria lista della spesa per utilizzarla da diversi dispositivi o per condividerla.
  2. Creare un social network. La stragrande maggioranza delle applicazioni sono create con lo scopo di mettere in relazione gli utenti tra loro. Anche i giochi sono un esempio con le loro classifiche o la possibilità di creare delle partite multiplayer.
  3. Statistiche, consumi e abitudini degli utenti. Molte applicazioni conservano le informazioni sull’utilizzo dell’applicazione. Per esempio nelle applicazioni di e-commerce, dove già l’autenticazione è utilizzata per la gestione degli acquisti, permette di sapere quali prodotti vengono acquistati, osservati e quant’altro.

Di seguito un esempio dell’applicazione realizzata a fine tutorial.

Motivi per creare un sistema di login online per la tua applicazione ne esistono a migliaia. In base alla tipologia di progetto, spetterà a te decidere quale sistema di autenticazione implementare.

Dico che spetterà a te perché in base alla tipologia d’applicazione che hai in mente, potrà essere necessario utilizzare una forma d’autenticazione rispetto ad un’altra.

Per il discorso fatto all’inizio, dove l’utente non ama i passaggi obbligatori o perdere tempo, l’autenticazione può impattare drasticamente sulla riuscita del tuo progetto.

Autenticazione con username/password o con Social Network come Facebook e Twitter?

Qui entrano in gioco argomenti che sfiorano e toccano appieno il Marketing delle applicazioni. Lo studio del mercato di riferimento è alla base delle scelta che ti spingerà a scegliere un sistema o un altro.

Se la tua applicazione è per una fascia d’età giovane è molto probabile che questi preferiscano utilizzare il login tramite Social Network. Facebook, Google o Twitter? Facebook è ovviamente il più gettonato ma, se per esempio siete sviluppatori Android, sapete benissimo che dovete dare rilevanza al Google Login dato che gli utenti Android hanno già un account Google.

Se invece la tua applicazione riguarda una fascia di utenti adulti o che non usa il Social (può anche essere che non volete far utilizzare i Social per questioni di privacy) allora è più scontato andare verso un’implementazione di login classica (con username, email e password).

La cosa positiva è che con Firebase potrai gestire tutti i sistemi di autenticazione, con Social e Non, indifferente dal tipo di progetto che stai sviluppando. Se ti renderai conto che ai tuoi utenti serve l’autenticazione con Facebook allora potrai aggiungerla successivamente e, ovviamente, collegare l’account esistente a quello social.

Fatta questa piccola premessa, sei pronto ad implementare l’autenticazione con Firebase e linguaggio Swift?

Il progetto di partenza

Per poter velocizzare tutto il tutorial, ho preparato un piccolo progetto di partenza che puoi scaricare dal link qui sotto. Il download non è obbligatorio e se vuoi puoi seguire il tutorial creando un tuo personale progetto.

Clicca il link di seguito per scaricare il progetto di partenza -> Download Da DropBox.

firebase auth login iOS

Lo Starter Kit è compatibile con Xcode 8.x ed il linguaggio Swift 3.0. Se stai ancora sviluppando con Xcode 7, questo progetto non sarà compatibile e dovrai ricrearlo da zero.

In fondo alla pagina troverai il download al progetto completo.

[su_spoiler title=”La struttura ed il contenuto dei file” style=”fancy”]

Il progetto presenta 3 ViewController principali. Prenditi due minuti per controllare la struttura dei file:

  1. LoginViewController: Qui l’utente eseguirà il login utilizzando l’email e la password. Se il login avverrà con successo, dove che sarà stato premuto il bottone “Login withEmail” (collegato alla IBAction classicLogin_clicked) verrà invocato il segue “segueToHome” che porterà a HomeViewController. Il bottone “New? Signup now!” invoca il segue “segueToSignup” che porterà a SignupViewController.
    import UIKit
    
    class LoginViewController: UIViewController {
    
        @IBOutlet var tf_email: UITextField!
        @IBOutlet var tf_password: UITextField!
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
        
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
        
        @IBAction func classicLogin_clicked(_ sender: UIButton) {
            
            guard let email = self.tf_email.text where !email.isEmpty else {
                print("\n [Error] Write Username \n")
                return
            }
            
            guard let password = self.tf_password.text where !password.isEmpty else {
                print("\n [Error] Write Password \n")
                return
            }
            
            
            self.performSegue(withIdentifier: "segueToHome", sender: nil)
        }
        
        @IBAction func signUp_clicked(_ sender: UIButton) {
            self.performSegue(withIdentifier: "segueToSignup", sender: nil)
        }
    
    
    }
    
  2. SignupViewController: Qui l’utente potrà registrare un nuovo account una volta che avrà cliccato su “Signup Now” (a cui è collegata la IBAction signup_clicked). A login avvenuto verrà portato a HomeViewController con l’invocazione dell’omonimo segue. Si può tornare indietro al LoginViewController con il bottone “Back to Login”.
    import UIKit
    
    class SignupViewController: UIViewController {
        
        @IBOutlet var tf_email: UITextField!
        @IBOutlet var tf_password: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        @IBAction func signup_clicked(_ sender: UIButton) {
            guard let email = self.tf_email.text where !email.isEmpty else {
                print("\n [Error] Write Username \n")
                return
            }
            
            guard let password = self.tf_password.text where !password.isEmpty else {
                print("\n [Error] Write Password \n")
                return
            }
            
            
            self.performSegue(withIdentifier: "segueToHome", sender: nil)
            
            
        }
    
        @IBAction func backToLogin_clicked(_ sender: UIButton) {
            self.dismiss(animated: true, completion: nil)
        }
    }
  3. HomeViewController: Se l’utente avrà registrato un nuovo account o avrà eseguito il login, verrà rimandato qui.
    import UIKit
    
    class HomeViewController: UIViewController {
    
        @IBOutlet var tf_helloUsername: UILabel!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
        
        
        @IBAction func logount_clicked(_ sender: UIButton) {
    
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController")
            self.show(vc, sender: nil)
       
        }
     
    }

All’interno del file ErrorMessageView è presente una struttura che ci servirà a creare un AlertViewController per comunicare all’utente gli eventuali problemi di login o registrazione.

import Foundation
import UIKit

struct ErrorMessageView {
    
    static func createAlert(title: String, message: String) -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
        
        alert.addAction(UIAlertAction.init(title: "Close", style: UIAlertActionStyle.cancel, handler: { (action) in
            alert.dismiss(animated: true, completion: nil)
        }))
        
        return alert
    }
}

[/su_spoiler]

Questi sono alcuni link utili alla comprensione di questo tutorial:

Cocoapod e Firebase

Prima di cominciare a mettere le mani al codice, devi settare correttamente sia il mio progetto che il tuo. Segui tutti i passaggi per l’integrazione di Firebase con la tua applicazione iOS.

  1. Crea una nuova applicazione su firebase.google.com (puoi anche utilizzarne una esistente).
  2. Aggiungi il Bundle Identifier della tua app iOS all’applicazione su Firebase.
  3. Scarica il file GoogleService-info.plist ed importalo dentro il progetto di Xcode.

Apri il terminale ed inizializza il progetto per poter integrare correttamente i framework di Firebase (prima il cd PercorsoCartella e successivamente “pod init”). Ho spiegato tutti i passaggi nel tutorial precedente.

Dato che integreremo autenticazione e database realtime, devi aggiungere i relativi framework. Quindi, vai nella cartella del progetto, apri il file Podfile ed inserisci il seguente testo:

platform :ios, ’10.0’

target 'NOMETUOPROGETTO' do

  use_frameworks!

  pod ’Firebase/Database’
  pod ’Firebase/Auth’

end

Successivamente, dal terminale, esegui il “pod install”. Una volta completato il processo, apri il progetto utilizzando il file nomeProgetto.xworkspace

Se stai passando da Xcode 7 alla versione 8, ricordati di passare il il platform: ios, ‘versione’, in 10.0. Una volta fatto questo, fai un “pod update” per aggiornare i vari framework (eseguilo periodicamente per avere tutto all’ultima versione).

Infine, spostati nell’AppDelegate.swift, aggiungi l’import Firebase:

import Firebase

Nel metodo finishLaunchingWithOptions, aggiungi il seguente codice. Questo aprirà la connessione con la tua applicazione su Firebase (ricordati di importare il file GoogleService-info.plist su Xcode):

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        FIRApp.configure()
        return true
    }

Impostazioni autenticazione su Firebase

Di default, tutte le applicazioni Firebase non permettono di autenticare gli utenti. Ogni singolo modulo d’autenticazione (Email, Facebook, Google ecc) deve essere abilitato manualmente. L’abilitazione di un sistema di Sign-In ti permetterà di utilizzare il framework FirebaseAuth importato poc’anzi.

Vai sulla console di Firebase ed entra dentro la tua applicazione. Spostati nel menu Auth:

Firebase auth pannello console

Nel pannello:

  • USERS: Vedrai la lista dei vari utenti loggati nell’applicazione. Il massimo di utenti visualizzati sarà 500, il resto li dovrai cercare manualmente.
  • SIGN-IN METHOD: Qui potrai abilitare o disabilitare i vari sistemi di autenticazione. Di default sono tutti disabilitati. In fondo alla pagina ci sono i link di OAuth nel caso utilizzi un sistema diverso, o un server, rispetto a quelli elencati. E ancor più sotto c’è un pannello che ti permette di abilitare la creazione di più account per la stessa email. Di default gli utenti potranno creare un solo account per email.
  • EMAIL TEMPLATES: Puoi modificare la forma ed il testo delle email di sistema. Per esempio, se vorrai modificare l’email per la conferma dell’email o per il reset della password, potrai farlo da qui.

Comincia abilitando la registrazione con Email e Password:

Autenticazione con email e password con firebase iOS

Adesso è tutto pronto per poter abilitare l’autenticazione con Firebase e linguaggio Swift!

Registrare un nuovo Utente

La registrazione di un nuovo utente tramite Email e Password, con Firebase per iOS, è qualcosa di estremamente semplice.

Il metodo da utilizzare è il createUser contenuto all’interno del framework FirebaseAuth. Questo metodo, che si presenta nella seguente forma, ha per parametri:

FIRAuth.auth()?.createUser(withEmail: String, password: String, completion: FIRAuthResultCallback?)
  • withEmail: L’email dell’utente. Noi la pescheremo dal TextField presente nel SignupViewController.
  • password: La password dell’utente da utilizzare per il login.
  • completion: La closure che contiene il risultato della creazione o meno dell’utente. Una volta implementata avrà due parametri, un FIRUser che conterrà il riferimento al nuovo utente creato su Firebase ed un NSError nel caso in cui ci fosse un problema nella registrazione. 

Entra nel file SignupViewController e, per prima cosa importa il FirebaseAuth:

import UIKit
import FirebaseAuth

class SignupViewController: UIViewController {

Successivamente, nel tuo bottone per la registrazione (nel mio progetto è la IBAction signup_clicked), aggiungi il seguente codice per la registrazione di un utente su Firebase:

FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
    guard error == nil else {
        print(" \n [ERROR] Can't create an Account \n   withError: \(error!.code, error!.localizedDescription) \n")
        return
    }
            
    print("\n Welcome \(user!.email!)")
    self.performSegue(withIdentifier: "segueToHome", sender: nil)
})

All’interno della closure completion, viene controllato lo stato della registrazione. Se il guard error == nil non viene attivato, cioè error è nil, allora la registrazione è avvenuta con successo. In questo caso, il parametro user (che è un oggetto FIRUser), viene riempito con i dati dell’utente (email e password).

Nel caso opposto, in cui la registrazione non fosse avvenuta, error sarà riempito con il problema in questione. I motivi per cui si possono verificare degli errori di registrazione possono essere diversi. I più famosi sono:

  • Utente già registrato.
  • Email non conforme (cioè manca il parametro @ e .).
  • Password debole (deve avere almeno 6 caratteri).
  • Caratteri non supportati (email scritte con caratteri speciali).

Infine, se tutto è andato per il verso giusto, viene invocato il segue verso la HomeViewController. Il segue è stato inserito all’interno della closure completion in quanto, questa, viene chiamata in asincrono rispetto all’esecuzione del codice (cioè viene chiamata in un secondo momento). Se avessi scritto il performSegue dopo il codice per l’autenticazione avresti spostato la visualizzazione in un VC diverso anche se l’autenticazione non fosse avvenuta.

Qui, un esempio del funzionamento:

Firebase iOS registrazione account

Nel caso in cui volessi comunicarlo all’utente e stai utilizzando il mio progetto, puoi utilizzare l’ErrorMessageView così:

guard error == nil else {
    print(" \n [ERROR] Can't create an Account \n   withError: \(error!.code, error!.localizedDescription) \n")
    let alert = ErrorMessageView.createAlert(title: "Can't create an Account!", message: "withError: \(error!.code, error!.localizedDescription)")
    self.show(alert, sender: nil)
    return
}

Questo farà comparire l’AlertView sullo schermo con le informazioni sull’errore. Ho parlato nel dettaglio delle Alert View in questo tutorial.

Login Utente

Una volta capito il funzionamento, le funzioni utilizzate sono tutte identiche. Nel caso del login, cambia solamente il nome della funzione ma il comportamento è uguale a quella utilizzata per la creazione dell’account.

Al solito, comincia importando il framework FirebaseAuth nel tuo LoginViewController:

import UIKit
import FirebaseAuth

class LoginViewController: UIViewController {

Il metodo utilizzato per il login è il signIn, che si presenta così:

FIRAuth.auth()?.signIn(withEmail: String, password: String, completion: FIRAuthResultCallback?)

Anche in questo caso, withEmail e password rappresentano i dati da utilizzare per il login. Questi dati dovranno essere già presenti su Firebase dato che stiamo provvedendo ad autenticare l’utente. Il completion contiene il risultato dell’operazione di login. É una closure che ha per parametri un FIRUser ed un NSError identici al caso analizzato del createUser.

Nel tuo bottone di login, dopo aver controllato la presenza di un testo nei due Text Field email e password, implementa il metodo per il login con Firebase:

FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
            
   guard error == nil else {
       print(" \n [ERROR] Can't Sign In \n   withError: \(error!.code, error!.localizedDescription) \n")
       return
   }
            
   print("\n Welcome \(user!.email!)")
   self.performSegue(withIdentifier: "segueToHome", sender: nil)
            
})

Semplice no? Se hai avuto qualche problema, fammelo sapere scrivendo un commento in fondo al tutorial.

Current User e passaggio diretto alla Home

Una delle particolarità del framework di Firebase è che riesce a mantenere la sessione dell’utente anche quando l’applicazione viene chiusa e riaperta. Cioè, una volta che è stato eseguito il login, viene memorizzato l’utente in locale.

L’utente vien conservato all’interno della proprietà currentUser dell’oggetto FIRAuth. Questa proprietà è nil se non esiste un utente oppure contiene l’istanza dell’utente come FIRUser:

FIRAuth.auth()?.currentUser

Potresti sfruttare questa proprietà per saltare la pagina di Login per spostarti direttamente alla Home. 

Queste operazioni di setting dell’app devono essere effettuate nell’AppDelegate e precisamente nel metodo didFinishLaunchingWithOptions che, come spiegato nel corso di sviluppo applicazioni iOS, viene richiamato prima della visualizzazione dello Storyboard.

All’interno del tuo AppDelegate e didFinishLaunchingWithOptions, sotto il FIRApp.configure(), aggiungi il seguente codice:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        FIRApp.configure()
        
        switch FIRAuth.auth()?.currentUser {
            
        case nil:
            print(" \n Current User is logged out \n  show LoginViewController \n")
            
        default:
            print(" \n Current User is logged in \n show HomeViewController \n ")

        }
        
        return true
    }

Per poter visualizzare il corretto ViewController in base al case del currentUser, devi settare lo StoryboardIdentifier ai ViewController in questione in modo da poterli istanziare da codice.

Spostati nel tuo StoryBoard, seleziona il LoginViewController e, nel pannello di Identity Inspector, attiva Use Storyboard Identifier e assegna lo stesso nome della classe come Storyboard ID:

Storyboard Identifier

La stessa identica operazione devi eseguirla anche per la HomeViewController.

Una volta attivati gli Storyboard ID, ritorna nell’AppDelegate e modifica il precedente codice con il seguente:

        switch FIRAuth.auth()?.currentUser {
            
        case nil:
            
            print(" \n Current User is logged out \n  show LoginViewController \n")
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController")
            self.window?.rootViewController = vc
            
        default:
            
            print(" \n Current User is logged in \n show HomeViewController \n ")
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeViewController")
            self.window?.rootViewController = vc
            
        }

Adesso, se avvierai l’applicazione dopo aver effettuato il login dell’utente, vedrai che passerà direttamente alla Home senza visualizzare la LoginViewController.

Autenticazione e Database Realtime

Con la sola e semplice autenticazione ci faresti ben poco. In base alle tue esigenze ti servirà l’autenticazione per archiviare correttamente le informazioni dell’utente.

Con Firebase hai già visto, dal tutorial precedente, come poter memorizzare i dati sul suo database realtime. Ora è arrivato il momento di studiare come creare una struttura dei dati per i tuoi utenti.

Da dove si parte?

Ad ogni utente registrato, viene fornito un ID univoco chiamato User UID. 

User UUID Firebase autenticazione

Questo ID potrai utilizzarlo per la scrittura della tua struttura dei nodi del database realtime di Firebase. Cioè, potrai prevedere una struttura del seguente tipo: basePathApp/users/UserUID.

Il setting dello spazio di archiviazione dell’utente puoi farlo in diversi punti dell’applicazione. Sarebbe più naturale farlo durante la fase di registrazione che eviterebbe la ridondanza e la ripetizione di codice che risulterà superfluo durante la normale esecuzione dell’applicazione.

Dato che noi abbiamo già creato un utente, ipotizziamo che l’applicazione permetta la memorizzazione nel database realtime di Firebase in un aggiornamento futuro. In questo caso puoi fare il setting dell’applicazione su AppDelegate o al momento del login.

Ad ogni modo, qualsiasi sia la strada che percorrerai, assicurati di eseguire il setting una ed una sola volta. Altrimenti il codice verrà sempre eseguito e sovraccaricherai di richieste il database di Firebase. Magari, potresti optare per un booleano salvato nell’NSUserDefaults per prevenire l’esecuzione ridondante del codice.

Dato che stiamo scrivendo un progetto di didattico, scriverai il tuo codice nella HomeViewController.

Nel tuo viewDidLoad del tuo VC che svolge il compito di Home, scrivi:

        guard let currentUser = FIRAuth.auth()?.currentUser else { return }
        
        let ref = FIRDatabase.database().reference()
        
        ref.child("users").child(currentUser.uid).updateChildValues(["display_email" : currentUser.email!])

Così facendo, non appena entrerai dentro la Home, verrà creato il relativo nodo per l’utente in questione:

Firebase login e database realtime iOS

FIRUser, proprietà ed aggiornamento dati

Scendiamo un po’ nel dettaglio della struttura dell’oggetto FIRUser.

Alcune proprietà le hai già potute assaporare in fase di registrazione e login. Oltre quelle né esistono altre che puoi modificare a tuo piacimento. La lista delle proprietà è la seguente:

  • email: L’email associata all’utente. In base alle impostazione del pannello Auth di Firebase, può esistere uno o più utenti che utilizzano la stessa email.
  • password: La pws deve essere composta ad almeno 6 caratteri. Questa è quella utilizzata con l’autenticazione classica con email.
  • displayName: Una sorta di Username dell’utente. Puoi utilizzarlo per creare una struttura degli utenti diversa da quella proposta con l’uid. Ricordati che nel Database Realtime le chiavi devono essere univoche. Di conseguenza, se utilizzi il displayName, non potranno esistere utenti con displayName uguale.
  • photoUrl: Contiene l’URL della foto profilo dell’utente. Non abbiamo studiato lo Storage dei file con Firebase, però puoi facilmente dedurre come qui andrà messo l’URL del nodo in cui è memorizzata la foto.
  • uid: Già incontrato, rappresenta un identificativo univoco per utente. Puoi star sicuro che non esisteranno utenti con uid uguale. Ecco perché l’ho utilizzato come nodo per la struttura degli utenti.

A parte la proprietà uid, tutte le altre sono opzionali di default. 

FIRUserProfileChangeRequest

Per poter aggiornare le proprietà del currentUser non basta modificare le sue proprietà. Bisogna utilizzare un oggetto specifico. Questo oggetto prende il nome di FIRUserProfileChangeRequest.

Il FIRUserProfileChangeRequest ti permette di settare i nuovi dati dell’utente e di aggiornarli in asincrono rispetto all’esecuzione dell’applicazione. Questo evita di bloccare l’app e controlla gli eventuali problemi d’aggiornamento.

Le proprietà che puoi modificare con questo oggetto sono solamente la displayName e la photoUrl. Per l’aggiornamento dell’email e password dovrai utilizzare un altro sistema che vedrai tra poco.

Una volta che recuperi l’istanza del currentUser, ti basterà recuperare l’oggetto FIRUserProfileChangeRequest dall’omonima proprietà del currentUser:

guard let currentUser = FIRAuth.auth()?.currentUser else { return }
let changeRequest = currentUser.profileChangeRequest()

Successivamente, questo changeRequest avrà le stesse proprietà del FIRUser corrente. Ti basterà modificarle con i nuovi dati. Per esempio, proviamo ad aggiungere il displayName all’utente.

Infine, queste modifiche le dovrai inviare a Firebase utilizzando il metodo commitChanges. Come unico parametro avrà un closure con l’eventuale errore nel caso non dovesse riuscire ad aggiornare i dati:

changeRequest.displayName = "Giuseppe Sapienza"
        
changeRequest.commitChanges { (error) in
    guard error == nil else {
        print(" \n \n Problem with ProfileChangeRequest \n \n   (\(error?.localizedDescription))")
        return
    }
            
    print("\n \n ProfileChangeRequest OK \n\n")
}

Nel progetto che trovi a fine lezioni ho inserito una textfield ed un bottone per l’aggiornamento del displayName dell’utente. In più ho salvato questo valore anche nel Database Realtime di Firebase:

Firebase autenticazione iOS e linguaggio swift aggiornamento displayName

updateEmail

Ti faccio solamente vedere i metodi e poi lascio a te l’implementazione nella tua applicazione.

let currentUser = FIRAuth.auth()?.currentUser

currentUser?.updateEmail("[email protected]") { error in
  if error != nil {
    // Errore
  } else {
    // Aggiornata con successo
  }
}

updatePassword

let user = FIRAuth.auth()?.currentUser

user?.updatePassword("nuovaPassword") { error in
  if let error != nil {
    // Errore.
  } else {
    // Password Aggiornata.
  }
}

Obbligo di nuova autenticazione

Alcune operazioni importanti come l’aggiornamento dell’email, password o cancellazione dell’utente richiedono la ri autenticazione dell’utente per poter essere confermate. Questa ri autenticazione viene richiesta allo scoccare di un tot tempo (non ben definito) passato dall’ultima autenticazione.

A tal proposito puoi verificare la presenza di questo errore quando uno dei metodi sopra citati presenta il codice FIRAuthErrorCodeCredentialTooOld.

In questo caso puoi ritornare alla pagina di login dell’applicazione oppure puoi far comparire una schermata in sovraimpressione in cui richiedere nuovamente i dati.

Logout Utente

Anche il logout è estremamente semplice. Ti basta invocare il metodo signOut() per poter scollegare definitivamente l’account dalla sessione corrente. Ovviamente spetterà a te riportare l’applicazione sulla pagina di login.

Il metodo signOut è passibile ad eccezione, quindi dovrai gestirlo con un do-try-catch.

Nel tuo bottone di logOut (nel mio progetto è il bottone con IBAction) aggiungi il seguente codice:

do {
   try FIRAuth.auth()?.signOut()
} catch let error {
    print(error)
}

Se vuoi anche spostare la visualizzazione alla pagina di login puoi utilizzare lo stesso sistema usato nell’AppDelegate. Ovvero puoi instanziare il vc LoginViewController subito dopo il try signOut().

    @IBAction func logout_clicked(_ sender: UIButton) {
        
        do {
            try FIRAuth.auth()?.signOut()
            
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController")
            self.show(vc, sender: nil)
            
        } catch let error {
            print(error)
        }
        
    }

Firebase e Login Facebook

Dato che il progetto è compatibile con iOS 10 ed Xcode 8, ci sono alcuni problemi con i framework di Facebook per l’autenticazione.

Non appena aggiorneranno il framework di Facebook, aggiornerò questa parte del tutorial.

Conclusione e Download

Secondo le regole Apple, non puoi utilizzare l’autenticazione con email e password senza aver inserito la privacy policy dei dati. Puoi creare una privacy policy della tua applicazione utilizzando iubenda.com. Una volta creata ti basterà mettere il link nella pagina di login o registrazione (in alternativa puoi usare un ViewController in cui aprire la WKWebView con la privacy policy).

Nel prossimo tutorial ti farò vedere come creare una piccola chat globale tra gli utenti della tua applicazione. Per il momento esercitati a mettere in relazione i dati creati da ogni utente con il Database Realtime.

Qui di seguito trovi il progetto completo.

[sociallocker]DropBox Download[/sociallocker]

Una volta scaricato dovrai:

  1. Installare i pod con il comando “pod install”.
  2. Aggiungere solamente il tuo file GoogleService-info.plist.

Se hai avuto qualche problema con il tutorial, fammelo sapere lasciando un commento.

Buona Programmazione!

Il database Realtime di Firebase con il linguaggio Swift

Il database Realtime di Firebase con il linguaggio Swift

Come si dice dalle mie parti, perché reinventare la ruota quando già qualcuno ha fatto il lavoro sporco per te?

Uno dei più grandi problemi di chi lavora singolarmente, o con una cerchia ristretta di sviluppatori, è la difficoltà di trattare e maneggiare tecnologie differenti per la realizzazione della propria applicazione.

A cosa mi riferisco?

Ormai le applicazioni sono tutte connesse alla rete. Tutte hanno le notifiche Push Online, tutte utilizzano un sistema di login personalizzato o tramite social e tutte hanno uno storage sul Cloud. In pratica, tutte le applicazioni sono sulle nuvole.

Ed è proprio questo il problema di cui parlavo. La mole di tecnologia e servizi, che utilizzano linguaggi e sistemi differenti rispetto al nostro amato linguaggio Swift, bloccano sul nascere le aspettative della maggior parte dei sviluppatori indipendenti.

Ma la storia ci insegna che dove c’è un problema c’è anche una soluzione.

La soluzione che io sto adottando per le mie applicazioni iOS si chiama Firebase. Ed In questo tutorial voglio darti tutti gli strumenti per utilizzare il database realtime di Firebase con il linguaggio Swift.

Firebase ios linguaggio swift

Cos’è Firebase?

Firebase è un SaaS (Software as a Service) online gratuito, acquistato da Google, che imbriglia dentro di se tutto il necessario per poter realizzare un’applicazione connessa al Cloud (con tutti i servizi che elencavo sopra) in maniera semplificata ed intuitiva.

Per semplificata intendo che non dovrai imparare nessun nuovo linguaggio di programmazione, non dovrai configurare server, connessioni e cosa più importante è gratuito (fino ad una certa soglia) e scalabile su tutte le piattaforme in circolazione (potrai utilizzare lo stesso Database di Firebase anche per progetti Android e Web).

Firebase ormai è una realtà più che consolidata e famosa, tanto da aver convinto Skyscanner e Shazam ad utilizzarlo come sistema di cloud storage.

Ti ho detto che Firebase è un servizio online. Per adesso te lo spiego così, poi vedremo nel dettaglio di che si tratta e cosa c’è realmente sotto la scocca.

Ma in pratica, a che serve Firebase e perché è importante per creare un’applicazione?

Ipotizziamo che la tua applicazione abbia bisogno di un Database Online. Ovvero hai bisogno di un posto online dove poter memorizzare le informazioni per poterle pescare dai tuoi dispositivi (come quello che hai utilizzato in locale con il Core Data) o perché, magari, questi dati devono essere letti ed interpretati dagli altri utenti della tua applicazione.

Senza Firebase, quello che avresti fatto sarebbe stato comprare uno spazio da un Host online, creare un Database SQL o NoSQL (vedi sotto) scrivere un sistema con un linguaggio Web (tipo PHP o Java) per poi interfacciare la tua applicazione iOS con quel sistema. Una procedura noiosa, lunga e complicata se non si conosce il funzionamento del web ed i suoi linguaggi.

Con Firebase ti basterà letteralmente e praticamente premere un bottone per creare uno spazio online che, grazie ad un framework progettato per il linguaggio Swift, potrai modificare e modellare direttamente dalla tua applicazione o da un’interfaccia Web intuitiva che ti mette a disposizione la stessa Firebase.

La caratteristica più di spicco di Firebase è la sua tecnologia Real Time (non mi piace parlare di Real Time però il termine rende bene l’idea).

I dati che scriverai nel database saranno immediatamente disponibili a tutti gli utenti connessi a quello spazio. Questa sua caratteristica è quella che ti potrebbe portare a realizzare una semplice chat online per la tua applicazione (ne parlerò in un tutorial separato).

Non voglio dilungarmi troppo nelle presentazioni di Firebase. Ti elenco le caratteristiche principali per poi cominciare a vedere come poter utilizzare il database Realtime di Firebase con il linguaggio Swift:

  • Realtime Database: I tuoi dati saranno accessibili ovunque ed in maniera istantanea. Es. scrivo un messaggio, lo salvo su Firebase e quel messaggio viene subito letto da un’altra applicazione.
    •  Firestore Database: Un evoluzione del Realtime Database, a discapito di un po’ di velocità, ha un’organizzazione migliore dei dati e delle query di ricerca più evolute.
  • Autenticazione: Puoi creare un tuo sistema di autenticazione (per esempio con email e password) oppure puoi utilizzare quelli dei Social in maniera semplificata.
  • Storage: Puoi archiviare su Firebase qualsiasi tipologia di dati. Dalle immagini, ai PDF ai normali tipi di dato come le stringhe e i numeri.
  • AdMob: Con Firebase accedi anche al framework AdMob che ti permette di monetizzare la tua applicazione tramite l’advertising (i banner).
  • Analytics: Ti mette a disposizione la potenza di Google Analytics per le statistiche della tua applicazione.
  • Altro: Vedi l’immagine sotto.

unnamed

In questa introduzione al database Realtime di Firebase con il linguaggio Swift voglio farti prendere confidenza con il sistema dei Database Realtime, analizzando ogni singolo componente del framework Firebase Database

Pronto per cominciare?

Allora vediamo insieme come utilizzare il database Realtime di Firebase con il linguaggio Swift!

Registrazione e creazione app Firebase

Il primo step che devi fare è quello di registrarti a firebase.google.com. Grazie al tuo account Google potrai entrare direttamente senza dover passare per la registrazione. In alto a destra troverai il bottone Accedi.

firebase creare applicazione ios xcode swift

Una volta fatto l’accesso, potrai entrare all’interno della Console di firebase per poter gestire i tuoi progetti.

Puoi entrare all’interno della console utilizzando anche questo link: console.firebase.google.com.

All’interno, se non hai mai utilizzato Firebase, troverai un enorme bottone con scritto Create New Project o Aggiungi Progetto. Premilo e successivamente ti chiederà di assegnare un nome al progetto e lo stato della tua residenza o società.

Il nome può essere indipendente dal nome che poi darai effettivamente alla tua applicazione iOS. Io ti consiglio di mettere nomi simili in modo da ricordarti che quel progetto Firebase è associato a quella determinata App dato che potrai avere N progetti su Firebase.

firebase-console-creazione-di-un-progetto-per-app-database

Questo progetto rappresenterà il ponte di congiunzione tra le tue applicazioni (sia iOS, Android che Web) e l’ecosistema di Firebase.

In pratica, utilizzerai questo progetto Firebase come contenitore e gestore delle informazioni delle tue applicazioni che vorranno utilizzare Firebase come sistema di cloud database (o le altre funzionalità).

Interfaccia Firebase

L’interfaccia della web app di Firebase è abbastanza intuitiva. Nel menu di destra puoi interagire con le varie componenti e feature di Firebase. Di default sono tutte disponibile e, ovviamente, verranno attivate solamente quando la tua applicazione ne avrà bisogno.

Questa suddivisione dei servizi ti permette di sfruttare solamente ciò che è necessario ai fini della tua applicazione. Per esempio, se la tua applicazione ha bisogno di utilizzare il login con Facebook o un login classico (email e password) potrai interagire solamente con la parte di autenticazione che è contenuto all’interno del pannello Auth.

Ogni caratteristica verrà trattata a suo tempo. In questo tutorial passeremo a rassegna solamente la parte di Database contenuta nell’omonimo pannello.

Collegare Firebase alla propria applicazione iOS

La prima cosa che devi fare, non appena crei un progetto su Firebase, è collegarlo ad una tua applicazione. Firebase ti permette di collegare lo stesso progetto Firebase a più applicazioni (Android, iOS e Web).

Dato che noi ci stiamo occupando di iOS. Imboccheremo questa strada.

creare-applicazione-firebase-ios-swift-xcode

Registrare l’App

Prima di procedere, crea una nuova applicazione come nuovo progetto Single View Application iOS.

Poi torna su Firebase. Nel primo step ti vengono richieste alcune informazioni dell’applicazione.

  • ID Bundle iOS: Quello che noi chiamiamo Bundle Identifier cioè l’identificatore univoco della tua applicazione. Questo Bundle ID lo generi in fase di creazione del progetto e puoi modificarlo o vederlo all’interno delle impostazioni del progetto e precisamente sul pannello General della tua app su Xcode.
    Questo campo è obbligatorio e deve essere obbligatoriamente uguale al Bundle Identifier che possiede la tua applicazione (nuova o esistente che sia).
  • App Store ID: Il tuo identificatore all’interno dell’App Store. Lo puoi trovare nell’URL della tua applicazione all’interno dell’App Store. Esempio, il numero alla fine dell’URL è il tuo App Store ID https://itunes.apple.com/us/app/yourapp/id123456789.
    Questo campo è facoltativo.

Per sicurezza, se hai creato un progetto o hai il progetto che vuoi collegare a Firebase. Questi sono i passaggi da fare per trovare il tuo Bundle ID:

Collegare app ios a firebase

Se tutto va per il verso giusto, alla pressione del tasto Registra App dovrebbe mandarti al secondo step di configurazione.

File di configurazione Xcode

Nel secondo step dovrai scaricare un file di configurazione che dovra inserire dentro Xcode:

Firebase file configurazione Xcode

Questo file, chiamato di default GoogleService-Info.plist contiene tutte le informazioni che serviranno alla tua applicazione per connettersi correttamente a tutti i servizi di Firebase ed al progetto che hai creato.

Una volta scaricato, devi passare questo file all’interno del tuo progetto Xcode. Ti basta trascinarlo all’interno del Target principale (la cartella dove ci sono tutti gli altri file) e selezionare la spunta Copy Item if Needed quando passerai il file (Questo creerà una copia del file all’interno della cartella del tuo progetto Xcode):

file configurazione firebase xcode ios linguaggio swift

Aggiungere gli SDK Firebase (CocoaPods)

Alla tua applicazione manca ancora il framework (un raggruppamento di classi) per poter effettivamente utilizzare Firebase.

Firebase utilizza CocoaPods per semplificare l’aggiunta dei suoi framework ad un progetto iOS. Se non sai cos’è CocoaPods e come utilizzarlo nella tua app, vai immediatamente a guardare questo tutorial di Mauro per metterti in pari con il mio tutorial. CocoaPods potrebbe cambiarti letteralmente l’esistenza!

Hai già installato CocoaPods?

Bene! Adesso apri il terminale e fai un piccolo check sulla versione del tuo CocoaPods scrivendo “pod –version“. Se la tua versione di CocoaPods installata è inferiore alla 1.3 ti esorto a fare l’upgrade se no non potrai proseguire con il tutorial. Per aggiornare CocoaPods ti basta scrivere “sudo gem install cocoapods” (con il sudo, dovrai scrivere la password subito dopo l’invio del comando).

pod --version
gem install cocoapods

Se hai avuto qualche problema, scrivimi un commento in fondo al tutorial.

Installare i Pod di Firebase

Il progetto della mia applicazione di prova si trova sul Desktop. Con il tuo terminale, muovi l’inspector all’interno del tuo progetto scrivendo il comando “cd IlPercorsoDelTuoProgetto/NomeTuoProgettoXcode“. Nel mio caso sarà:

cd Desktop/FirebaseAppTest

A questo punto, puoi inizializzare CocoaPods all’interno del tuo progetto per prepararlo a ricevere i framework. Adesso scrivi:

pod init

Questo processo creerà un file chiamato PodFile che conterrà le informazioni sui Framework da installare.

installare pod firebase xcode ios

Entra dentro la cartella del tuo progetto, apri il file PodFile e sostituisci il contenuto con il seguente:

platform :ios, '10.0'

target 'FirebaseAppTest' do
  use_frameworks!

  pod 'Firebase/Core'
  pod 'Firebase/Database'

end

Nel tuo PodFile, se hai incollato il mio codice, assicurati di cambiare il nome del target. Nel mio caso il progetto si chiama FirebaseAppTest, quindi scriverò “target ‘FirebaseAppTest’ do“.

I due pod fondamentali all’utilizzo di Firebase e del suo Database Realtime sono:

  1. Firebase/Core: deve sempre esserci in qualsiasi progetto che utilizza Firebase
  2. Firebase/Database: farà in modo che cocoa pods installi l’sdk per il database

L’ultimo passaggio nella console è quello, dopo aver salvato il PodFile modificato, di installare i pod scrivendo:

pod install

cocoa pods e firebase xcode ios swift

Da ora in poi dovrai aprire il tuo progetto Xcode utilizzando il file con estensione .xcworskspace. Se già avevi aperto Xcode con il progetto, chiudilo e riaprilo passando da questo nuovo file. Se ti piace scrivere dalla console puoi aprirlo scrivendo “open nomeTuoProgetto.xcworskspace“.

Aprendo il progetto dal file xcworkspace dovresti avere una struttura dei file simile a questa:

struttura progetto xcode con firebase ios

Se dovessi aver avuto qualche problema di qualsiasi sorta, dato che questa è la parte più incasinata del processo di collegamento tra Firebase e l’applicazione iOS, ti prego di comunicarmelo così da poterlo sistemare insieme.

Nell’ultimo passaggio di Firebase, quello che ti chiede di inserirei il codice nell’app, vai pure avanti senza problemi. Lo vedremo insieme più sotto.

Tipologie di Database

Un database è un contenitore di informazioni. Pensalo come ad una libreria, dove ci sono tanti scaffali divisi con qualche particolare logica, per esempio per autore, e dove dentro ad ogni scaffale ci sono le informazioni salvate (nel caso della libreria, sarebbero i libri).

Chi va in libreria ad affittare o comprare un libro non interessa come questo Database sia organizzato e composto. Vuole semplicemente prendere il libro in questione e andarsene. Ed infatti, dentro ad una Libreria c’è sempre un commesso che penserà a recuperare il libro dato che conosce la struttura della libreria.

I database dell’informatica sono simili a delle librerie.

Sono software specializzati nella memorizzazione dei dati in maniera persistente (cioè che non scompaiono quando chiudiamo l’app o spegniamo il computer/server).

Come esistono librerie che organizzano i libri in maniera differente, esistono anche Database che salvano in memoria i dati in maniera completamente differente.

Esistono due tipologie di database o almeno queste sono quelle più utilizzate:

Database SQL

I Database che si dicono relazionali, organizzano le informazioni in una sorta di tabella divisa per righe e colonne. Dove ogni colonna contiene una particolare informazione.

Per esempio una tabella di un database relazione potrebbe essere Utente. Dentro Utente le colonne dividono le informazioni in età, nome, cognome ecc. Nelle righe vengono messi i valori da memorizzare.

La seguente tecnologia è alla base del Core Data (strumento che ti ho fatto utilizzare per la memorizzazione in locale). L’accesso a queste informazioni avviene mediante l’utilizzo dei linguaggi SQL e per tanto vengono chiamati database SQL

Database noSQL

Dall’altro lato esistono i Database non Relazionali. Le informazioni vengono memorizzate, non più in tabelle, ma in strutture di tipo chiave-valore come, più o meno, i dizionari del linguaggio swift.

Questa è la tecnologia che sta alla base dei Database Realtime di Firebase.

Un esempio, poi lo vedremo nel dettaglio, potrebbe essere quello di un dizionario che organizza le informazioni in questo modo:

"peppeSap" = {
    "nome" = "Giuseppe"
    "cognome" = "Sapienza"
    "età" = "22"
}

La chiave dell’informazione è l’username dell’utente, “peppeSap”, associato a questa chiave c’è un dizionario di coppie chiavi valore. Dove le chiavi vengono sempre messe a sinistra e i valori a destra.

La struttura richiama quella della sintassi JSON (il link rimanda ad un tutorial dove, nella prima parte spiego cos’è un file JSON).

Questi Database non relazioni, non utilizzano SQL come metodo di lettura ed interazione e, di conseguenza, vengono chiamati Database NoSQL (Not Only SQL dato che alcuni potrebbero anche utilizzare SQL).

Il Database Realtime di Firebase

Entra dentro il tuo progetto Firebase e spostati nel pannello Database.

Qui ti chiederà di selezionare o il Realtime Database o il Cloud Firestore. Seleziona il Realtime Database.

creare-il-primo-database-realtime-con-firebase

l’area di Realtime Database è divisa in 4 sezioni:

  • Dati: Qui troverai i dati memorizzati all’interno del Database dai tuoi utenti. Tutte le applicazione scriveranno le informazioni qui dentro in una struttura che tra poco analizzerai.
  • Regole: L’accesso ai dati del tuo Database Realtime può essere bloccato o sbloccato in Lettura e Scrittura. Di default, come dice la scritta in azzurrino con la stella, i tuoi utenti devono essere autenticati per poter leggere e scrivere nel database. Noi sbloccheremo questa funzionalità in modo che si possa leggere e scrivere sul Database anche da non autenticati.
  • Backup: Il piano a pagamento di firebase ti permette di eseguire dei backup automatici.
  • Utilizzo: Ti farà vedere quanti accessi simultanei avverranno di giorno in giorno.

interfaccia del database realtime di firebase

La struttura dei Dati

Il database realtime di firebase archivia i dati in strutture simili a file JSON. Un file di tipo JSON come ho già spiegato in questo articolo è simile ad un dizionario del linguaggio swift.

Il punto d’ingresso o la base del database è quel dato che vedi al centro dell’area bianca. Nel mio esempio è:

testappxcoding : null

La chiave del mio database è il nome del mio progetto e, associato a questa chiave, c’è un valore nullo.

Una cosa importante da analizzare è che l’accesso alle informazioni avviene anche mediante gli URL. In alto l’url di default è composto, nel mio esempio, da https://testappxcoding.firebaseio.com/ questo url punta direttamente alla chiave testappxcoding del Database.

I punti d’ingresso del database, che vengono rappresentati dalle chiavi del DB, vengono chiamati Nodi. Quindi, testappxcoding è il primo nodo o Root Node del Database. Da qui potrai accedere a tutti i dati che salveranno gli utenti.

Ovviamente, si può personalizzare il database creando dei sotto nodi. Ogni sotto nodo avrà un URL univoco che sarà dato dalla combinazione dei nodi superiori più il nodo in questione.

Esempio

Ti faccio vedere un esempio abbastanza semplice e poi ti insegnerò a creare delle strutture dati funzionali.

Ipotizziamo che la tua applicazioni, tra le tante cose, permetta di creare un profilo utente condivisibile con gli altri utenti dell’applicazione. L’utente generico si registra all’applicazione creando un nickname ed inserendo dei dati come nome, età e provenienza.

La struttura del tuo Database Realtime dovrà, per tanto, conservare tutti gli utenti suddivisi per nickname. Ma dove vanno inseriti questi utenti?

Se li aggiungessi come sotto nodi del nodo principale rischierei di mischiare, in fasi successive, informazioni diverse tra loro.

Per questo motivo, si può creare un sotto nodo, che serve da contenitore per gli utenti dell’applicazione, chiamato “utenti“. Sotto il nodo “utenti” verranno inseriti degli ulteriori sotto nodi che avranno per chiave il nickname dell’utente registrato.

Quello che verrà fuori è una struttura simile alla seguente:

struttura esempio utenti database realtime firebase

Nell’esempio che vedi sopra, gli utenti vengono archiviati in un sotto nodo chiamato utenti. Dentro “utenti” ci sono due oggetti chiamati “giuseppesapienza” e “lucappalardo” che, oltre ad essere due utenti che si sono registrati alla tua applicazione, sono essi stessi dei sotto nodi.

Il nodo “utenti“, può essere raggiunto utilizzando il link https://testappxcoding.firebaseio.com/utenti/ che ti farà vedere tutti i suoi sotto nodi.

Puoi andare in fondo fino a raggiungere il valore associato ad una chiave. Per esempio, se vuoi leggere il nome dell’utente giuseppesapienza, potrai scrivere https://testappxcoding.firebaseio.com/utenti/giuseppesapienza/nome

Aggiungere dati dalla dashboard

Se sposti il mouse vicino al nodo principale del tuo Database vedrai che comparirà un tasto a forma di più ed un tasto x. Premendo il tasto + puoi aggiungere un nuovo sotto nodo.

Se lo premi vedrai che potrai aggiungere due valori. A sinistra va la chiave ed a destra il valore associato a quella chiave. Nei casi dei nodi che servono da contenitori, come il caso di “utenti“, non è necessario assegnargli un valore.

Però, per poter inserire nodi senza valore, questo deve possedere dei sotto nodi con dei valori. Mi spiego meglio. Se crei il sotto nodo “utenti”, sotto a questo dovrà almeno esserci un sotto nodo riempito con dei valori.

Proviamo a ricreare la struttura che hai visto sopra.

Premi sul tasto + e aggiungi “utenti“. Dato che “utenti” serve da contenitore, premi il tasto + e aggiungi un sotto nodo chiamato “ilTuoUsername” (ipotizziamo che tu ti sia registrato all’app). A questo ulteriore nodo che conterrà i dati dell’utente, premi il tasto + e aggiungi le chiavi “web”, “nome” ed “età” con i rispettivi valori:

esempio inserimento nodi firebase realtime

La modifica e l’eliminazione sono altrettanto semplici. Ti basta passare il mouse sopra un valore da modificare per cambiarlo o sopra la x per eliminarlo. Eliminare un nodo significa eliminare tutti i sotto nodi.

Esercizio 0. Prova ad aggiungere un nuovo utente, con gli stessi attributi.
Esercizio 1. Crea un nuovo nodo principale, chiamato “news” e aggiungigli delle news divise per titolo. Dai come attributi per ogni news il titolo, il luogo ed una descrizione.

Come strutturare correttamente i Dati

Ipotizziamo che un utente della tua applicazione voglia salvare delle note personali associate solamente al suo account. Cioè, l’utente vuole una lista personale di note.

La tentazione più forte, in questi casi, è quella di innestare all’interno dell’utente un nuovo sotto nodo chiamato note. Cosa che crea non pochi problemi:

esempio struttura dati da evitare firebase realtime database

Il problema principale di una struttura del genere è che, quando accederai ad un nodo, per esempio “utenti“, scaricherai tutti i dati e sotto nodi che si troveranno all’interno.

Ora, è vero che il Database Realtime di Firebase permette di innestare i dati fino a 32 livelli di profondità, però, come loro stessi consigliano nella guida ufficiale, è preferibile utilizzare una struttura dei dati Flat.  

Potresti, quindi, optare per un sotto nodo del nodo principale chiamato note dove suddividerai le note degli utenti per username:

struttura flat database realtime firebase

Tra le tantissime proprietà che derivano dall’utilizzare questa struttura c’è indubbiamente sia quella della facilità di lettura (intesa anche come velocità dell’elaborazione) che quella di possedere una struttura di facile manutenzione e upgrade.

Se vuoi dei consigli su come strutturare correttamente i dati del tuo database, scrivi un commento in fondo alla pagina oppure entra nella nostra community Slack o Facebook.

Regole d’accesso

Di default, per poter accedere ai dati del Database realtime di Firebase, i tuoi utenti devono essere autenticati utilizzando uno dei sistemi di Auth di Firebase (email, facebook, twitter ecc).

Dato che in questa guida non stiamo trattando l’autenticazione con Firebase, devi obbligatoriamente sbloccare la lettura e la scrittura dei dati per gli utenti non loggati.

Per farlo, spostati nel pannello Rules del tuo database:

regole utilizzo database realtime firebase

Non voglio addentrarmi molto nelle regole d’accesso al database Realtime, ci ritornerò in un altro articolo, per ora è importante solamente capire che si può modificare l’accesso a qualsiasi nodo del Database.

Di default il blocco rules, così com’è scritto, blocca l’accesso in read e write a tutti gli utenti che non sono autenticati “auth != null“. Per poter rimuovere questo blocco, ti basta cambiare il valore di read e write in true oppure in “auth == null“.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Comparirà un enorme bottone con scritto Pubblica. Cliccalo per confermare la modifica.

Ricordati che, in fase di distribuzione dell’applicazione sull’app store dovrai configurare correttamente le regole d’accesso se non vuoi che qualcuno si inserisca nel tuo Database (dato che può essere letto e modificato da tutti, anche dal web).

Scrivere sul Database di Firebase con linguaggio Swift

Dovresti avere già creato il progetto iOS con il framework di Firebase già importato (con CocoaPods). Adesso è arrivato il momento di vedere come connettere il Database Realtime di Firebase all’app iOS con il linguaggio Swift.

Per prima cosa bisogna inizializzare la connessione con Firebase.

Vai nel tuo AppDelegate, importa il framework Firebase e, dentro il metodo didFinishLaunchingWithOptions, scrivi FirebaseApp.configure():

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        return true
    }

}

La classe FirebaseApp rappresenta il punto d’ingresso del framework della connessione con il progetto che hai creato su Firebase.

Il metodo configure() serve a creare la connessione con il progetto. Non hai bisogno di inserire parametri perché il metodo configure() prende le informazioni direttamente dal file GoogleService-info.plist.

Se avvii l’applicazione, in console dovrebbero spuntare dei messaggi. Non preoccuparti del contenuto di questi messaggi. L’importante è che l’app non vada in crash.

Dati possibili

Prima di mettere in piedi un’applicazione, devi imparare ad utilizzare le funzioni di base per il salvataggio dei dati sul database di firebase.

All’interno del Database Realtime puoi salvare diversi tipi di dato tra cui:

  • Stringhe
  • Numeri (Int, Double, Float ecc)
  • Bool
  • Array
  • Dizionari

Database Reference, il punto d’ingresso

Ti ho detto che i dati vengono organizzati in una struttura a nodi dove ogni nodo è raggiungibile da un link univoco. Di conseguenza, per poter scrivere sul Database dall’applicazione è necessario, come prima cosa, definire in quale nodo poter salvare le informazioni.

Il framework di Firebase ha già un oggetto che ti permette di raggiungere il nodo principale dell’applicazione. L’oggetto in questione si chiama Database ed è contenuto all’interno del framework FirebaseDatabase.

Per poter accedere al riferimento del nodo principale, ti basterà utilizzare due metodi sull’oggetto singleton Database. Il primo è database() che restituisce l’istanza del Database ed il secondo, da invocare sul precedente, è reference() che ritorna il link al suddetto nodo.

Adesso spostati nel ViewController. Importa il framework FirebaseDatabase e, nel viewDidLoad, scrivi:

import UIKit
import FirebaseDatabase

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let ref = Database.database().reference()
        print(ref)
        
    }

}

Se avvii l’applicazione, in console, dovresti vedere l’url che punta al nodo principale.

Child, accedere ad un sotto nodo

Se hai seguito gli step precedenti, nel tuo database Firebase dovrebbero esserci due sotto nodi principali. Uno chiamato “utenti” ed uno “note“. Se non li hai, provvedi a crearli.

Per creare un sotto nodo, ricordati che devi obbligatoriamente aggiungere dei dati reali (come per esempio un utente o una nota).

Da codice, per poter accedere ad un sotto nodo puoi utilizzare il metodo child(pathString: String) che restituisce il FIRDatabaseReference (cioè l’url) del sotto nodo passato come parametro. Quindi, se adesso scrivi (sempre nel viewDidLoad):

let ref = Database.database().reference()
print(ref)

let ref_utenti = ref.child("utenti")
print(ref_utenti)

funzione child accedere a sotto nodo database realtime firebase

Il print del ref_utenti dovrebbe stampare l’url del sotto nodo “utenti“.

Nel caso in cui non esistesse il sotto nodo che stai provando ad accedere, l’app non andrà in crash bensì il Database creerà un sotto nodo temporaneo nell’attesa che, successivamente, gli vengano inseriti dei dati.

Grazie al metodo child ti è possibile navigare all’interno della struttura dei dati. Un altro utilizzo del child è quello di inserire un path più dettagliato. Per esempio, puoi scrivere più path all’interno del child per raggiungere un nodo specifico in un’unica istruzione:

let ref_giuseppe = ref.child("utenti/giuseppesapienza")

La stessa sintassi può essere scritta così:

let ref_giuseppe = ref.child("utenti").child("giuseppesapienza")

SetValue, scrivere e aggiornare un dato

Per poter scrivere sul Database, il primo sistema messo a disposizione è il metodo setValue(value: Any?) che accetta in ingresso un qualsiasi oggetto. Nella pratica accetterà uno dei tipi di dato che ti ho elencato poc’anzi.

Il metodo setValue va invocato su un nodo ben specifico. Per esempio, se volessi aggiungere un nuovo utente, invocherò il setValue sul nodo “utenti“.

Proviamo ad aggiungere un nuovo utente.

Ti ricordo che i il database di Firebase organizza i dati come dei dizionari del linguaggio Swift. Quindi, il nostro nuovo utente, che chiameremo “xcoderA“, avrà come dati il seguente dizionario:

let dati_xcoderA = [
    "nome" : "XcoderA XCodino",
    "età" : "22",
    "sito-web" : "xcoding.it"
]

Le chiavi del dizionario corrispondono esattamente alle chiavi che ho dato all’utente che ho creato dal pannello web di firebase. Ovviamente puoi aggiungere qualsiasi tipologia di dato e chiave, gli altri utenti non verranno compromessi. É giusto, però, utilizzare delle chiavi comuni a tutti gli utenti per facilitare la lettura in secondo momento.

Adesso ti basta accedere al child(“utenti”). Su questo poi dovrai creare un sotto nodo con il nome del nuovo utente, ovvero un child(“xcoderA”), ed infine invocare il setValue passandogli il dizionario dati_xcoderA:

let ref = Database.database().reference()

let dati_xcoderA = [
    "nome" : "XcoderA XCodino",
    "età" : "22",
    "sito-web" : "xcoding.it"
]

ref.child("utenti").child("xcoderA").setValue(dati_xcoderA)

Se adesso avvii l’applicazione, vedrai che il Database verrà immediatamente aggiornato con l’aggiunta del nuovo utente e dei suoi relativi dati:

setValue firebase realtime database linguaggio swift ios xcode

La sintassi analoga è la seguente:

ref.child("utenti/xcoderA").setValue([
    "nome" : "XcoderA XCodino",
    "età" : "22",
    "sito-web" : "xcoding.it"
])

A questo punto è facile intuire come un sotto nodo, aggiunto da codice, viene creato solamente quando il setValue assegna un valore a quel nodo. Nel nostro esempio, nel database non era presente il nodo xcoderA, questo è stato aggiunto solo quando il setValue gli ha assegnato il dizionario di valori.

Fai attenzione perché, il setValue, sovrascrive tutto il nodo. Per esempio, se volessi modificare il nome all’utente e scrivessi così:

ref.child("utenti/xcoderA").setValue(["nome":"nome aggiornato"])

Questo sovrascriverà tutto il contenuto del nodo “xcoderA” perché gli stai assegnando un nuovo dizionario che contiene solo una coppia di chiave-valore. Ed infatti:

problema-setvalue-su-database-realtime-firebase

Come fare allora?

Per poter risolvere il problema con il setValue, puoi accedere al nodo in questione, cioè il nome dell’utente ed eseguire lì la modifica:

ref.child("utenti/xcoderA").child("nome").setValue("nome aggiornato")

Esercizio 2. Prova ad aggiungere un nuovo utente o una nuova nota utilizzando il setValue. Successivamente modifica uno dei suoi valori.

updateChildValues, aggiornare uno o più nodi

Nel caso volessi modificare un particolare valore associato ad una chiave, senza evitare la sovrascrittura dell’intero nodo o il dover utilizzare il setValue in maniera impropria, puoi utilizzare il metodo updateChildValues(value: Any?).

Questo metodo sovrascrive solo i valori associati a quelle determinate chiavi.

Per esempio, se volessi modificare l’età dell’utente xcoderA, scriverei:

ref.child("utenti/xcoderA").updateChildValues(["età" : "23"])

Ma così com’è il metodo non ha un granché di differente rispetto al setValue. Per poter apprezzare la potenza dell’updateChildValues devi immaginarlo applicato a qualcosa di più complesso.

Nella struttura che ho creato ho due sotto nodi, “utenti” e “note“. Ogni utente ha un omonimo nodo che serve a contenere le note salvate. Adesso, pensiamo ad un utilizzo più complesso, ogni utente ha una proprietà che tiene conto del numero di note salvate.

Cioè, avremmo una struttura del genere:

esempio-struttura-database-realtime-firebase-ios-swift

Il nostro utente xcoderA ha una proprietà chiamata n_note che tiene conto del numero di note memorizzate sul database. A questo punto, vogliamo aggiungere una nuova nota e aggiornare anche quel valore.

Grazie all’updateChildValue, invocato sul root, puoi scrivere, come chiave del dizionario passato al parametro, il path da aggiornare e come valore il nuovo dato da aggiungere:

let ref = Database.database().reference()

ref.updateChildValues([
    "note/xcoderA/nota-2" : [
        "titolo" : "La seconda nota",
        "descrizione" : "Una seconda descrizione"
    ],
    
    "utenti/xcoderA/n_note" : 2
])

Grazie a questo metodo sei riuscito a fare entrambe le cose contemporaneamente:

esempio-updatechildvalues-firebase-database

Esercizio 3. Prova ad aggiungere un nuovo utente ed una nuova nota utilizzando il metodo updateChildValues.

childByAutoId

Le note dell’utente io le ho numerate utilizzando i numeri decimali, cioè nota-1, nota-2 ecc. Il problema di questa sintassi deriva dal fatto che il database è di tipo realtime e non offre un controllo sulla scrittura simultanea da parte di più utenti sullo stesso nodo.

Mi rispiego.

Ipotizziamo che esista la nota-1. Due utenti stanno provando a scrivere la nota-2 contemporaneamente ma con valori di titolo e descrizione diversi. Quale delle due versioni di nota-2, il database, dovrà accettare?

Questo problema, che sembra banale, apre degli scenari complessi che ricadono in una scienza ben specifica che prende il nome di gestione della concorrenza. É qualcosa di talmente brutto che chi la nomina finisce inesorabilmente per morire di autocombustione.

Fortunatamente ci sono degli eroi, all’interno del team di sviluppo di Firebase, che hanno pensato ad un sistema intelligente per risolvere questo spiacevole problema.

Il metodo childByAutoId crea un sotto nodo univoco che esce fuori dalla combinazione del tempo (inteso come minuti,ora e giorno) e dall’UUID dell’applicazione (cioè un codice univoco assegnato ad ogni applicazione installata dall’utente).

Il fatto che questo AutoId è una funzione del tempo genera dei sotto nodi che vengono automaticamente ordinati cronologicamente. Dunque i nodi saranno organizzati da quello più vecchio al più recente (ordinamento crescente). Utile in moltissime situazioni come news feed e altre tipologie d’applicazioni che operano in funzione del tempo.

Proviamo ad utilizzarlo. Elimina tutte le note presenti sotto xcoderA e, nel tuo viewDidLoad, scrivi:

ref.child("note").childByAutoId().setValue([
    "titolo" : "una nota con child Auto Id",
    "descrizione" : "descrizione bla bla"
])

childbyautoid-firebase-database-realtime

Nel caso in cui volessi utilizzare l’autoId, dato che è univoco per ogni invocazione del metodo, puoi conservarlo in una variabile utilizzando il parametro .key di ogni DatabaseReference.

Ipotizziamo che l’utente, quando crea la nota, la voglia anche salvare tra le sue preferite. Potremmo utilizzare l’autoId generato per salvarlo in un nuovo sotto nodo chiamato “note_preferite”: 

let nota = ["titolo" : "titoloA",
            "descrizione" : "una descrizione"]

let ref = Database.database().reference()

/*Genero un nuovo Auto Id (Non è importante il ref in cui viene utilizzato) */
let autoId = ref.childByAutoId().key

ref.child("note").child("xcoderA").child(autoId).setValue(nota)

ref.child("note_preferite").child("xcoderA").setValue([
    autoId : true
])

utilizzare-un-autoid-come-chiave-database-realtime-firebase

Leggere dal Database

Leggere dal Database di Firebase è un processo semplice ma con una potenzialità incredibile.

Il sistema che si utilizza per leggere i dati dal Database di Firebase è completamente diverso rispetto al classico sistema: Chiamata -> Elaborazione dal DB -> attesa -> Risposta -> Elaborazione dei dati dall’app.

Con Firebase anche la lettura avviene in realtime.

Tra i metodi di lettura proposti dal framework FirebaseDatabase quello più interessante è l’Observer. Un observer è un oggetto speciale che rimane in attesa dell’aggiornamento di un particolare nodo. L’observer rimane a guardare per un tempo indefinito, un nodo del DB, avvisandoti quando questo viene investito da un evento.

Tutti questi particolari eventi possono essere intercettati dall’Observer in realtime:

  • childAdded: Viene invocato per tutti i sotto nodi esistenti e, successivamente, per ogni nuovo sotto nodo che verrà aggiunto al nodo che si sta osservando.
  • childRemoved: Un sotto nodo viene eliminato.
  • childMoved: Un sotto nodo viene mosso dal nodo osservato.
  • childChanged: Uno sotto nodo viene aggiornato.
  • value: Viene invocato per ogni evento che si verifica sul nodo osservato, compresi i suoi sotto nodi.

Questi eventi vengono descritti dall’enum DataEventType

Per esempio, se l’utente dovesse aggiungere dei nuovi sotto nodi, io potrò sapere quali ha aggiunto utilizzando un observer childAdded. Oppure se verrà eliminato un nodo, io potrò essere notificato dell’accaduto tramite un observer in modalità childRemoved.

Capita la logica, vediamo insieme come utilizzarli!

Il metodo observe

Il metodo che dovrai utilizzare si chiama observe (chi l’avrebbe mai detto) e viene attaccato ad un particolare nodo per osservarne gli eventi descritti dal DataEventType.

Il metodo ha due parametri:

  1. eventType: Un case dell’enum DataEventType. Cioè quelli che ti ho elencato sopra .value .childAdded .childRemoved e così via
  2. with: Una closure che ha come parametro un oggetto chiamato DataSnapshot che contiene un Any con il dato acchiappato dall’observer.

Più facile ad utilizzarlo che a descriverlo.

Aggiungi il seguente codice all’interno del tuo viewDidLoad:

ref.child("utenti").observe(.value) { (snap) in
    print(snap)
}

L’observe che ho aggiunto al childutenti” legge l’evento DataEventType.value (cioè tutti gli eventi che si verificano sul nodo).

La closure viene richiamata quando firebase restituisce i dati richiesti dall’observer (se non ti ricordi cos’è una closure leggi questo tutorial del corso gratuito sul linguaggio swift). I dati vengono conservati dentro l’oggetto snap che è di tipo DataSnapshot.

All’interno della closure ho stampato il contenuto dello snap:

observe-value-firebase-realtime-database-linguaggio-swift-ios

Pazzesco no?

Estrarre i dati dal DataSnapshot

Un DataSnapshot è una fotografia del nodo nel momento in cui viene intercettato l’evento. Questo vuol dire che, nel momento in cui un dato viene modificato mentre si sta leggendo lo snap, quell’aggiornamento verrà intercettato dall’evento successivo.

Il valore trasportato dallo snap viene conservato nella proprietà value che è un Any opzionale nel caso in cui l’observer non riesca a trovare dei dati all’interno del nodo. 

Puoi castare questo oggetto nel tipo di dato che contiene il nodo per poterlo trattare all’interno dell’applicazione.

Nel nostro caso, dentro il nodo utenti, c’è un dizionario di dizionari. Dove ogni chiave è il nome dell’utente ed il valore associato il dizionario che contiene i suoi dati.

let ref = Database.database().reference()


ref.child("utenti").observe(.value) { (snap) in
    // 1
    guard let value = snap.value else {
        print("Il nodo osservato non contiene dati")
        return
    }
    
    // 2
    guard let dict = value as? [String : Any] else {
        print("Il nodo non è un dizionario")
        return
    }
    
    // 3
    for (key, value) in dict {
        print("leggo i valori di: ", key)
        
        // 4
        guard let dict_value = value as? [String : Any] else {
            print("i dati di \(key) non sono un dizionario")
            return
        }
        
        // 5
        let nome = dict_value["nome"] as! String
        let età = dict_value["età"] as! String
        let n_note = dict_value["n_note"] as! Int
        
        print(key, nome, età, n_note)
    }
    
}
  1. Controllo che lo snapshot contenga dei dati. Infatti i dati reali sono contenuti dentro alla sua proprietà value
  2. Trasformo il value che è un Any in un [String : Any] dato che i dati del nodo utenti sono rinchiusi in un dizionario
  3. Eseguo il ciclo che mi scorre tutti i valori contenuti nel dizionario. La key conterrà la chiave dell’utente, per esempio “xcoderA”, mentre il value il dizionario che si trova all’interno
  4. Dato che value l’ho considerato un Any (vedi il punto 2), devo convertirlo nel suo tipo reale che, a sua volta, è un [String:Any] dato che i suoi valori sono sia String (come il nome e l’età) che Int (la chiave n_note)
  5. Estratto i valori dal dizionario dell’utente e li casto nel loro tipo reale

Se dovessi aver avuto dei problemi in questa parte, lasciami pure un commento e vedrò di aiutarti. Non aver paura, ci siamo passati tutti! 

Evitare l’observe .value

Ma come? ora che me lo hai fatto conoscere mi dici di non usarlo?

C’è un motivo e voglio fartelo vedere.

Se adesso provi a modificare, cancellare etc un dato dal nodo utenti il tuo observe .value verrà riavviato.

Questo significa che ri-scaricherà tutti i nodi ed i sotto nodi contenuti dal nodo utenti anche se tu dovessi muovere una virgola. Provare per credere, fai qualche modifica al database mentre hai l’app avviata e guarda cosa accade in console.

Cosa usare allora?

ObserveSingleEvent

Nel caso in cui volessi osservare per una sola volta i dati di un nodo, potrai utilizzare il metodo observeSingleEvent. Il metodo d’utilizzo è uguale al precedente, con l’unica differenza che tutti gli eventi futuri non verranno acchiappati da questo observer:

ref.child("utenti").observeSingleEvent(of: .value) { (snap) in
    
}

Questo metodo è utilissimo per una buona prevenzione delle risorse messe a disposizione da Firebase. Ti ricordo che il limite del piano gratuito è di 100 utenti contemporaneamente connessi.

L’observeSingleEnvent ti permetterà di accedere singolarmente ad una risorse per poi scollegarti subito dopo.

Observe childAdded

Una delle alternative migliori, all’evento .value per la lettura dei dati, è quella di utilizzare il .childAdded. 

Il childAdded funziona così:

  • La prima volta che viene avviato legge tutti i sotto nodi presenti nel nodo a cui viene attaccato.
  • Una volta letti tutti, quando verrà aggiunti nuovi child al nodo assegnato verrà restituito lo snap del nuovo child.

Puoi tradurlo così: Leggi, uno per volta, tutti i child presenti nel nodo e poi rimani in attesa che ne vengano aggiunti di nuovi.

Vediamo insieme con un esempio.

esempio-struttura-database-per-evento-childadded

Sono loggato dentro l’app con l’utente xcoderA e voglio recuperare le mie note per stamparle in una tabella. Per risolvere questo problema potrei attaccare un observe .childAdded al nodo note/xcoderA:

let ref = Database.database().reference()
let utente = "xcoderA"

ref.child("note").child(utente).observe(.childAdded) { (snap) in
    
}

Cosa mi butterà fuori lo snap?

Ti aiuto io (oppure fai un print e osserva la console).

Se sei stato attento, ti ho detto che il .childAdded legge i nodi uno per volta. Quindi, dentro lo snap, ci sarà un singolo child. Questo significa che snap.value sarà un dizionario contente i valori della nota, quindi la descrizione ed il titolo.

let ref = Database.database().reference()
let utente = "xcoderA"

ref.child("note").child(utente).observe(.childAdded) { (snap) in
    guard let dict = snap.value as? [String:String] else {
        return
    }
    
    let titolo = dict["titolo"]
    let descrizione = dict["descrizione"]
    
    let key = snap.key
    print(key, titolo!, descrizione!)
    
}

L’observe essendo di tipo childAdded non risponderà alle modifiche che avverranno ai vari nodi, bensì verrà svegliato solamente quando si inserirà un child sotto “note“.

Un sistema decisamente più semplice, sicuro e meno pesante dal punto di vista computazionale.

Quando ti capita, quindi, cerca di usare questo evento rispetto al value.

Rimuovere un Observer

Gli observer classici, cioè che non sono di tipo observeSingleEvent, rimangono in ascolto per tutta la vita dell’applicazione.

Spesso e volentieri, per il discorso fatto sopra, è consigliato bloccarli o eliminarli quando questi non sono più utili per l’app.

Il metodo observe restituisce un numero Int che può essere utilizzato per arrestare l’esecuzione dell’observer.

Per arrestare un observe puoi utilizzare il metodo removeObserver(withHandle: UInt), sul nodo in cui è stato attaccato l’observerche accetta in ingresso l’id dell’observer da arrestare:

let observer_id = ref.child("note").child(utente).observe(.childAdded) { (snap) in
}

ref.child("note").removeObserver(withHandle: observer_id)

In alternativa puoi utilizzare il removeAllObservers per eliminarli tutti:

ref.child("note").removeAllObservers()

Lo ripeto, per rimuovere un observer devi invocare uno dei due metodi proposti nel rispettivo nodo d’appartenenza. Altrimenti non verranno eliminati. Allo stesso modo, il removeAllObservers, non elimina tutti gli observers dei sotto nodi ma solo quelli del nodo in cui viene invocato.

Ordinare i dati del Database

Vengono dette Query quei comandi che vengono interpretati dai database e che permettono a questo di poter elaborare i suoi dati in base a delle particolari condizioni espresse nel comando (la Query).

Per esempio, se io avessi una lista di punteggi di un giocatore e li volessi ordinare per punteggio decrescente, chiederei al database di farlo tramite una query.

Firebase database realtime query swift ios xcode

Il Database Realtime di Firebase utilizza questo sistema di domande, o query, per poter cambiare il risultato restituito dagli observer. Quindi, queste query vengono eseguite prima di attaccare un observer ad un nodo.

Si può ordinare il risultato di un observe utilizzando una delle seguente query:

  • queryOrderedByKey: Ordina il risultato prendendo in considerazione le chiavi del nodo. Per esempio, se un nodo ha come chiavi “B”, “C”, “A”, l’observer ordinerà le chiavi in ordine alfabetico, quindi “A”, “B”, “C”.
  • queryOrderedByValue: Ordina il risultato prendendo in considerazioni i valori associati alle varie chiavi del nodo.
  • queryOrderedBy(byChild: String): Ordina prendendo in considerazione il valore di una delle chiavi del sotto nodo.
  • queryOrderedByPriority: Ordina per priorità assegnate al nodo. Non ho trattato questo aspetto e quindi lo lascio in sospeso (in caso chiedimi per commento).

QueryOrderedByValue

Per esempio, nel database ho creato un nodo punteggi, dove ci sono come chiavi i nomi degli utenti e come valori i punteggi. Per leggere i dati in maniera ordinata ti basterà scrivere:

ref.child("punteggi").queryOrderedByValue()

Successivamente dovrai far partire l’observer con tipologia d’evento .childAdded:

ref.child("punteggi").queryOrderedByValue().observe(.childAdded) { (snap) in
    print(snap)
}

Ti ricordo che l’evento .childAdded attiva l’observer, la prima volta su tutti i nodi presenti e successivamente per ogni sotto nodo aggiunto. Questo farà si che alla prima esecuzione stampi tutti i nodi in maniera ordinata:

/*
Snap (matteo) 10
Snap (luca) 20
Snap (Giuseppe) 35
Snap (ciccio) 40
Snap (delia) 90
*/

Invece d’usare il .childAdded che viene invocato per ogni nodo, non posso usare l’evento .value?

L’evento .value non restituisce il dato corretto della query. É probabile, ma non sono sicuro, che il motivo derivi dal fatto che con il .childAdded si eviti quel famoso problema della concorrenza tra gli utenti. Infatti l’ordinamento dei dati, o in generale le query, possono impiegare del tempo per essere eseguite e quindi si verrebbero a creare delle discrepanze o dei rallentamenti con l’interazione con il DB.

QueryOrderedByChild

Ho un’app in cui vengono pubblicati dei posts che gli utenti possono leggere. Ogni volta che un utente legge un post viene incrementata la proproprietà views.

queryOrderedByChild firebase realtime linguaggio swift ios

Oltre alla visualizzazione ordinata cronologicamente (data di default dall’autoId) vorrei mostrare i miei articoli in base al numero di views.

In questo caso non posso utilizzare il queryOrderedByValue sul nodo posts perché la proprietà views si trova all’interno dei singoli post.

Per ordinarli dovrò utilizzare il queryOrderedByChild sul nodo posts che mi permetterà di accedere ad una proprietà intera di ogni singolo post. Questo funzionerà perché il .childAdded legge ogni singolo post (come se si attaccasse al nodo idX) e per questo il queryOrderedByChild potrà leggere la proprietà views:

ref.child("posts").queryOrdered(byChild: "views").observe(.childAdded) { (snap) in
    print(snap)
}

Ed ecco che magicamente ci tira fuori i posts ordinati per views:

/* console:
Snap (id2) {
    content = "Ci siamo! \U00c9 arrivato il nostro corso sul linguaggio swift";
    title = "Ecco il corso gratuito sul Linguaggio Swift";
    views = 700;
}
Snap (id3) {
    content = "Utilizziamo firebase per loggare i nostri utenti";
    title = "Come eseguire il login con Firebase";
    views = 900;
}
Snap (id4) {
    content = "Nasce il gruppo facebook ed il canale Slack";
    title = "xCoding community";
    views = 1300;
}
Snap (id1) {
    content = "Uno dei SaaS pi\U00f9 utilizzati del momento";
    title = "Il database realtime di firebase";
    views = 4000;
} */

Filtrare i dati

Oltre alle classiche operazioni di ordinamento, è possibile abbinare dei filtri agli observer in modo da cercare solo una tipologia di dato ben specifica.

I metodi in questione sono:

  • queryLimited(toFirst: Int): Restituisce i primi N elementi passati al parametro.
  • queryLimited(toLast: Int): Restituisce gli ultimi N elementi.
  • queryStartating(atValue: Any?): Restituisce N elementi che hanno un valore maggiore o uguale al valore di startValue (un numero, una stringa o un carattere).  Per esempio, se vuoi cercare tutti gli utenti che hanno per nome “paperino”, questo è il metodo che fa per te.
  • queryEnding(atValue: Any?): L’opposto del precedente. Esempio, se vuoi cercare tutti gli utenti che hanno il nome che finisce per “sapienza”.
  • queryEqual(toValue: Any?): Restituisce gli elementi che sono uguali al value passato alla funzione.

Questi metodi possono essere usati singolarmente oppure in combinazione con i metodi di ordinamento. Per esempio, se voglio stampare il post che ha il numero maggiore di views userei il .queryOrdered(byChild) in combinazione con il queryLimited(toLast):

ref.child("posts")
    .queryOrdered(byChild: "views")
    .queryLimited(toLast: 1)
    .observeSingleEvent(of: .childAdded)
{ (snap) in
    print(snap)
}
  1. Perché l’observeSingleEvent e non l’observe?
    In questo modo, dato che voglio un solo elemento, l’observe verrà invocato una sola volta e poi distrutto.
  2. Perché il queryLimited(toLast)?
    L’ordinamento che utilizza il queryOrdered è crescente. Quindi il più grande è l’ultimo elemento restituito dall’observe. Con il queryLimited(toLast: 1) riesco a prendere solamente l’ultimo valore.

Una cosa importante da ricordare è che non puoi unire né due query di ordinamento né due query di filtraggio nella stessa istruzione.

Considerazioni

Per qualsiasi problema o vuoi un consiglio su come strutturare i tuoi dati, scrivimi pure un commento qui sotto. Firebase è eccezionale se usato correttamente, quindi non aver timore di chiedere qualsiasi cosa.

Ormai il mondo va verso la velocità dello sviluppo. Utilizzare un app come questa ti permette di risparmiare tempo e sopratutto denaro dato che i costi sono davvero irrisori se paragonati agli strumenti e capacità che mette a disposizione.

Io ormai la utilizzo quasi per tutti i progetti. Ha potenzialità che vanno ben oltre il normale database: Analytics, login, Machine Learning, Crash Analytics ed A/B Testing. Cosa che, se le dovessi sviluppare da solo o in team, impiegheremmo mesi se non addirittura anni.

Se vuoi continuare con Firebase ho scritto un altro tutorial sul Login e Signup con Firebase. Nel frattempo, comincia a prendere confidenza con il Database Realtime di firebase con il linguaggio Swift.

Buona Programmazione!