Popatrzmy na konstrukcję obiektów w Swift trochę odwrotnie. Jeśli znacie narzędzie JSONDecoder(), to na pewno widzieliście wywołanie typu
let decoder = JSONDecoder()
let product = try decoder.decode(A.self, from: json)
Na podstawie typu przekazanego jako parametr, funkcja decodekonstruuje instancję obiektu/struktury typu A. jednak nie widać tu bezpośredniego wywołania konstruktora typu.
let a = A()
Jak więc jest to zrealizowane? Spróbujmy napisać własny przykład, w którym stworzymy funkcję fabrykującą obiekty pewnego typu.
W funkcji decode z klasy JSONDecoder wymagane jest, aby podany typ był zgodny z protokołem Codable. Zrobimy coś podobnego. Potrzebujemy protokołu, który pozwoli zagwarantować, że wykorzystane typy będą pozwalały na konstrukcję ich obiektów. Protokół Initializablebedzie więc wymagać istnienia funkcji init().
protocol Initializable {
init()
}
Mając tak zdefiniowany protokoł możemy przejść do implementacji funkcji fabrykującej. Skorzystamy z notacji <> pozwalającej na stworzenie funkcji generycznej.
func createInstance<T>(typeThing:T.Type) -> T where T:Initializable{
return typeThing.init()
}
Funkcja createInstance jest więc sparametryzowana typem
Dzięki temu jesteśmy pewni, że typ implementuje funkcję init(), a zatem poprawna będzie instrukcja return typeThing.init(). W przypadku metatypów nie można użyć zwykłej notacji typeThing()tylko trzeba jawnie określić wywołanie konkretnego konstruktora.
Konstrukcja obiektu dowolnego typu spełniającego określone warunki będzie więc wymagała użycia:
let a = createInstance(typeThing: SomeTestType.self)
Oczywiście pozostał jeszcze do zdefiniowania sam typ SomeTestType, który będąc zgodnym z protokołem Initializablemusi implementować konstruktor init().
class SomeTestType : Initializable {
var property:String
required init() {
property = "It works!"
}
}
Gotowe.