Stored properties ed extension: un approccio Swift puro
Le extension di Swift non hanno stored properties. Una soluzione Swift pura con una struct Holder statica, poi con un dizionario indicizzato per istanza.
Tradotto dall’inglese con l’aiuto dell’IA e rivisto dall’autore.Leggi l’originale

Una delle domande più frequenti sulle extension di Swift riguarda le stored properties. Quante volte avete desiderato una stored property nella vostra ultima, fantastica extension, per parametrizzare qualche comportamento?
Come forse sapete, Swift non permette le stored properties nelle extension. È una scelta di design: "Extensions may not contain stored properties."
L'errore più comune è cercare di aggirare il limite con qualcosa del genere:
extension UIViewController {
var myStoredProperty:Bool {
get {
return self.myStoredProperty
}
set(newValue) {
self.myStoredProperty = newValue
}
}
}Anche se il codice compila senza errori, ci si accorge presto che è completamente sbagliato: il riferimento ciclico alla computed property fa girare il codice in loop finché l'heap non è saturo di chiamate quasi identiche.

Il secondo approccio comune è cercare su Google. Si trovano alcuni trucchetti ingegnosi per aggiungere qualcosa che assomigli al concetto di stored property da usare dentro le extension (vedi i riferimenti).
Uno dei workaround più (ab)usati si affida al runtime di Objective-C, con una soluzione basata sulle funzioni objc_getAssociatedObject e objc_setAssociatedObject.
Anche se queste soluzioni non hanno nulla di sbagliato, un approccio Swift puro sarebbe molto più elegante. È questo il motivo del post: provare a creare un'extension Swift pura con supporto alle stored properties.
La soluzione proposta è molto semplice. Parte dall'assunto che le extension non accettano stored properties ma solo computed properties. Elaborando il concetto di struct, che sono tipi per valore, finiamo per aggirare il design delle extension aggiungendo di fatto il concetto di stored property. Il tutto in puro Swift.
Un proof-of-concept vale più di mille parole. Ecco qui:
import UIKit
extension UIViewController {
struct Holder {
static var _myComputedProperty:Bool = false
}
var myComputedProperty:Bool {
get {
return Holder._myComputedProperty
}
set(newValue) {
Holder._myComputedProperty = newValue
}
}
}La struct Holder conterrà il valore privato che la nostra computed property espone al mondo, dando l'illusione del comportamento di una stored property.
29/03: Aggiornamento
Come fatto notare da Paolo, anche se l'approccio funziona, andrebbe rivisto almeno in due punti per risolvere quanto segue:
-
Visibilità private per la struct Holder (al momento chiunque può accedere alle struct statiche di servizio e cambiarne il valore senza ulteriori controlli).
-
Istanze multiple della classe estesa. L'approccio proposto soffre di un problema molto fastidioso: se si creano più istanze della classe estesa, ogni istanza condividerà lo stesso valore statico della struct Holder. Il problema dipende dalla natura della keyword static. Con un po' di fantasia possiamo essere creativi e risolvere la questione.

Quello che segue dovrebbe fare al caso nostro:
extension UIViewController {
private struct Holder {
static var _myComputedProperty = [String:Bool]()
}
var myComputedProperty:Bool {
get {
return Holder._myComputedProperty[self.debugDescription] ?? false
}
set(newValue) {
Holder._myComputedProperty[self.debugDescription] = newValue
}
}
}Qui abbiamo sfruttato una specie di hash table per conservare tutti i possibili valori che appartengono a istanze diverse della classe. Notate che self.debugDescription è un approccio davvero "grezzo", e andrebbe elaborato un modo più conciso e intelligente per distinguere i riferimenti di istanze diverse. In ogni caso, come poc sembra funzionare.
30/03: Aggiornamento
Non immaginavo che ci fosse un interesse così grande per questo argomento! Grazie a tutti. C'è ancora margine di miglioramento in questo approccio e, dopo una discussione molto cordiale con il mio amico Davide, ho deciso di dare un seguito all'articolo per includere alcuni dei suoi suggerimenti e migliorare l'hash table. Dato che usiamo una static var per emulare una hash table, non c'è più ragione di affidarsi alla struct. D'accordo. Questo farà risparmiare un bel po' di righe di codice. Vorrei anche migliorare il meccanismo della chiave della hash table usando l'indirizzo di memoria, come fanno i puntatori. La nuova versione dell'approccio è la seguente:
extension UIViewController {
private static var _myComputedProperty = [String:Bool]()
var myComputedProperty:Bool {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
return UIViewController._myComputedProperty[tmpAddress] ?? false
}
set(newValue) {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
UIViewController._myComputedProperty[tmpAddress] = newValue
}
}
}
Molto, molto meglio! C'è ancora un piccolo problema di "memory leak", ma questa è un'altra storia.
Happy swifty coding! 🖖🏻
