Le lingue che supportano funzioni di prima classe consentono di utilizzare funzioni e metodi come qualsiasi altro oggetto o valore. Puoi passarli come argomenti, salvarli nelle proprietà o restituirli da un’altra funzione. In parole d’ordine, la lingua tratta le funzioni come”cittadini di prima classe”.

Mentre Swift non è la prima lingua a supportare questo modo di gestire le funzioni, è normalmente una funzionalità che si vede in linguaggi più dinamici come JavaScript o Lua. Quindi combinare il robusto sistema di tipo statico di Swift con funzioni di prima classe diventa una combinazione piuttosto interessante e può permetterci di fare alcune cose piuttosto creative 😀.

Questa settimana, diamo un’occhiata ad alcuni modi diversi in cui le funzioni di prima classe possono essere utilizzate in Swift!

Instabug: Risolvi bug, crash e altri problemi molto più velocemente utilizzando le tracce dettagliate dello stack, i registri di rete e gli eventi dell’interfaccia utente che Instabug attribuisce automaticamente a ogni segnalazione di bug. Utilizzato sia da me che da migliaia di team di sviluppo iOS in tutto il mondo. Provalo gratuitamente e integralo con una sola riga di codice.

Passando le funzioni come argomenti

Iniziamo con le basi. Poiché le funzioni possono essere utilizzate come valori, significa che possiamo passarle come argomenti. Ad esempio, diciamo che vogliamo aggiungere una serie di sottoview a una vista. Normalmente, potremmo fare qualcosa del genere:

let subviews = subviews.forEach { subview in view.addSubview(subview)}

Il codice sopra funziona e non c’è niente di sbagliato in esso. Ma se sfruttiamo le funzioni di prima classe, possiamo effettivamente ridurre la sua verbosità parecchio.

Quello che possiamo fare è trattare il metodo addSubview come una chiusura di tipo (UIView) -> Void (poiché accetta una vista da aggiungere e non restituisce nulla). Questo corrisponde perfettamente al tipo di argomento che forEach accetta (una chiusura di tipo (Element) -> Void, e in questo caso il tipo Element è UIView). Il risultato è che possiamo passare view.addSubview direttamente come argomento alla nostra chiamata forEach, in questo modo:

subviews.forEach(view.addSubview)

Che figata! However Tuttavia, una cosa da tenere a mente, è che quando si utilizzano metodi di istanza come chiusure come questa si mantiene automaticamente l’istanza finché si mantiene la chiusura. Questo non è affatto un problema quando si passa una funzione come argomento di chiusura non escape come sopra, ma per sfuggire alle chiusure è qualcosa di cui essere consapevoli per evitare i cicli di conservazione.

Per ulteriori informazioni sull’escape rispetto agli argomenti di chiusura non escape e sull’acquisizione, controlla “Acquisizione di oggetti in chiusure Swift”.

Passando gli inizializzatori come argomenti

La cosa interessante è che non sono solo le funzioni e i metodi che possono essere utilizzati come funzioni di prima classe in Swift – puoi anche usare gli inizializzatori in questo modo.

Ad esempio, diciamo che abbiamo una serie di immagini per cui vorremmo creare viste immagine e che vogliamo aggiungere ciascuna di queste viste immagine a una vista stack. Usando le funzioni di prima classe possiamo ottenere tutto quanto sopra usando una semplice catena di map e forEach:

images.map(UIImageView.init) .forEach(stackView.addArrangedSubview)

Quello che mi piace della strutturazione del codice in questo modo è che diventa molto dichiarativo. Invece di loop nidificati for stiamo semplicemente dichiarando quello che vogliamo che sia il risultato. Ovviamente c’è un equilibrio tra codice dichiarativo, compatto e leggibilità, ma per operazioni semplici come le precedenti penso che sfruttare le funzioni di prima classe possa essere super bello.

Puoi trovare altri esempi e casi d’uso per passare gli inizializzatori come chiusure in “Simple Swift dependency injection with functions” e “Time travelling in Swift unit tests”.

Creazione di riferimenti al metodo di istanza

Immergiamoci un po ‘ più a fondo nel meraviglioso mondo delle funzioni di prima classe 😉. Una cosa che mi ha sconcertato per il tempo più lungo è stato il fatto che ho ricevuto suggerimenti di completamento automatico del metodo di istanza quando volevo chiamare un metodo statico. Prova a digitare UIView. in Xcode per vedere cosa intendo, ottieni ogni metodo di istanza come suggerimento 🤔.

All’inizio pensavo che si trattasse di un bug Xcode, ma poi ho deciso di indagarlo. Si scopre che per ogni metodo di istanza di un tipo, esiste un metodo statico corrispondente che consente di recuperare quel metodo di istanza come chiusura, passando un’istanza come argomento.

Ad esempio, possiamo utilizzare quanto segue per recuperare un riferimento al metodo removeFromSuperview per una determinata istanza UIView :

let closure = UIView.removeFromSuperview(view)

Chiamare la chiusura di cui sopra sarebbe esattamente la stessa di chiamare view.removeFromSuperview(), che è interessante, ma è davvero utile? Diamo un’occhiata ad alcuni scenari in cui l’utilizzo di questa funzione può effettivamente portare ad alcuni risultati piuttosto interessanti.

XCTest su Linux

Un modo in cui uno dei framework Apple utilizza questa funzionalità è quando si eseguono test utilizzando XCTest su Linux. Sulle piattaforme di Apple, XCTest funziona utilizzando il runtime Objective-C per cercare tutti i metodi di test per un determinato caso di test e quindi eseguirli automaticamente. Tuttavia, su Linux non esiste un runtime Objective-C, quindi ci richiede di scrivere un po ‘ di boilerplate per far funzionare i nostri test.

Per prima cosa, dobbiamo dichiarare un dizionario statico allTests che contiene una mappatura tra i nostri nomi di test e i metodi effettivi da eseguire:

Passiamo quindi il dizionario sopra alla funzione XCTMain per eseguire i nostri test:

XCTMain()

Sotto il cofano, questo sta usando la funzione di essere in grado di estrarre i metodi di istanza usando il suo equivalente statico, che ci consente di fare semplicemente riferimento alle funzioni per nome in un contesto statico, pur consentendo al framework di generare metodi di istanza da eseguire. Abbastanza intelligente! Without

Senza questa funzione, avremmo dovuto scrivere qualcosa del genere:

Chiamando un metodo di istanza su ogni elemento in una sequenza

Prendiamo questa funzione per un giro noi stessi. Proprio come siamo stati in grado di passare il metodo di istanza di un altro oggetto come argomento a forEach, non sarebbe bello se potessimo anche passare un metodo di istanza che vogliamo che ogni elemento in una sequenza esegua?

Ad esempio, diciamo che abbiamo una serie di sottoview che vogliamo rimuovere dalla loro superview. Invece di dover fare questo:

for view in views { view.removeFromSuperview()}

Non sarebbe bello se potessimo farlo invece:

views.forEach(UIView.removeFromSuperview)

La buona notizia è che possiamo, tutto ciò che dobbiamo fare è creare una piccola estensione su Sequence che accetti uno di questi metodi di istanza staticamente referenziati. Poiché sono funzioni che generano una funzione (Functionception! 😂 ) il loro tipo sarà sempre (Type) -> (Input) -> Output, quindi per la nostra estensione possiamo creare un sovraccarico forEach che accetta una chiusura di tale tipo:

Ora possiamo facilmente chiamare metodi di istanza su ogni membro di qualsiasi sequenza! Implementing

Implementazione di target/action senza Objective-C

Diamo un’occhiata a un altro esempio. In UIKit, il modello di destinazione/azione è molto comune, per tutto, dall’osservazione dei clic dei pulsanti alla risposta ai gesti. Personalmente mi piace molto questo modello, dal momento che ci consente di utilizzare facilmente un metodo di istanza come callback senza doversi preoccupare del problema del ciclo di conservazione di cui abbiamo discusso in precedenza (quando si fa riferimento a un metodo di istanza come chiusura).

Tuttavia, il modo in cui target/action è implementato in UIKit si basa sui selettori Objective-C (motivo per cui è necessario annotare i metodi di azione privata con @objc). Diciamo che volevamo aggiungere il pattern target/action a una delle nostre viste personalizzate e diciamo che vogliamo farlo senza fare affidamento sui selettori Objective-C. Potrebbe sembrare un sacco di lavoro e che renderà le cose terribilmente complicate, ma grazie alle funzioni di prima classe – è abbastanza semplice! start

Iniziamo definendo un Action typealias come una funzione statica che restituisce un metodo di istanza per un determinato tipo e input:

typealias Action<Type, Input> = (Type) -> (Input) -> Void

Quindi, creiamo la nostra vista. Creeremo un ColorPicker che consente all’utente di scegliere un colore in un’app di disegno e aggiungere un metodo per aggiungere un’azione di destinazione & ad esso. Terremo traccia di tutte le osservazioni come chiusure, e ogni volta che viene eseguita una chiusura generiamo un metodo di istanza per il target specificato ed eseguirlo, in questo modo:

La cosa interessante è che possiamo effettivamente utilizzare le funzioni di prima classe ancora di più sopra. Utilizzando l’API map su Optional, possiamo generare il metodo di istanza e chiamarlo in una volta sola, in questo modo:

observations.append { view in target.map(action)?(view)}

Infine, usiamo la nostra nuova API target/action in un CanvasViewController, che presenterà il nostro ColorPicker. Proprio come aggiungeremmo un’azione target & a un UIButton o UIGestureRecognizer, possiamo semplicemente passare il controller di visualizzazione stesso e un metodo di istanza da eseguire, come questo:

Digitare obiettivi sicuri & azioni senza selettori Objective-C o rischi per perdite di memoria, usando solo poche righe di codice – piuttosto bello! –

Supporto Swift da Sundell controllando questo sponsor:

Instabug: Risolvi bug, crash e altri problemi molto più velocemente utilizzando le tracce dettagliate dello stack, i registri di rete e gli eventi dell’interfaccia utente che Instabug attribuisce automaticamente a ogni segnalazione di bug. Utilizzato sia da me che da migliaia di team di sviluppo iOS in tutto il mondo. Provalo gratuitamente e integralo con una sola riga di codice.

Conclusione

Funzioni di prima classe è una caratteristica molto potente. Essendo in grado di utilizzare funzioni e metodi in modo molto più dinamico, possiamo ottenere risultati piuttosto interessanti e può essere davvero utile quando si implementano determinati tipi di API.

Tuttavia, nelle famose parole di zio Ben; con grande potere arriva una grande responsabilità. Mentre penso che sia super utile per conoscere questi tipi di caratteristiche e come funzionano, è anche importante esercitare un po ‘ di moderazione quando li si utilizza. Il nostro obiettivo dovrebbe essere sempre quello di creare API che siano piacevoli e facili da usare e di scrivere codice che sia facile da leggere & mantenere. Le funzioni di prima classe possono sicuramente aiutarci a raggiungere questo obiettivo, ma se prese troppo lontano possono anche portare al contrario. Come sempre, la mia raccomandazione è di sperimentare, provare queste funzionalità e vedere di persona se e come possono essere utilizzate nel proprio codice.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.