Swift Generic Types

Aytuğ
4 min readMay 9, 2021

--

Selamlar, Kodluyoruz & Trendyol iOS Bootcamp eğitimim devam ederken daha önceden bilsek bile gerçek hayatta hiç uygulamadığım bir kavramdan söz etmek istiyorum. Bu eğitim ile artık “Yahu burası generic yapsam ne tatlı olur ama” diyorum. Bakalım kimmiş bu generic type.

Generic Type Nedir?

Generic type, yazdığınız kodun farklı tipler ile tekrar kullanılabilir haline getirmenizi sağlayan kavramdır. Temelde aynı işi yapan fakat bunu farklı tipler üzerinde gerçekleştiren kodlarınızı tek bir çatı altında toplamanıza olanak sağlar. Böylelikle kod tekrarını azaltır ve daha temiz çalışmış oluruz.

func sum(firstValue: Int, secondValue: Int) -> Int {    firstValue + secondValue}func sum(firstValue: Double, secondValue: Double) -> Double {    firstValue + secondValue}

Basit bir örnek ile ele almak gerekirse, 2 adet sayıyı toplamak istiyoruz. Fakat bu sayıların tipi Int veya Double olabilir. Bu yüzden 2 farklı fonksiyon yazdık. Aynı işi yaptıklarına hemfikiriz. Yalnızca 2 fonksiyonun aldığı ve döndürdüğü değer tipi farklı. Bunu kodu daha güzel yazmak adına kendimiz bir tip uyduralım. Temsili ve anlamlı olması açından “Type” ın T’si bizim tipimiz olsun.

func sum<T>(firstValue: T, secondValue: T) -> T {    firstValue + secondValue}

Burada fonksiyon adımızdan hemen sonra kendi oluşturduğumuz tipi <> işaretleri arasına yazıyoruz. Artık kendi tipimizi bu fonksiyon için kullanabiliriz. Int, Double demek yerine genel bir isim verip T dedik. Artık dışarıdan gelen 2 tane aynı tipteki bir değeri toplayabilir ve tekrar o tipteki değeri döndürebiliriz.

Bir şeyi unuttuk değil mi? Her tipteki 2 değer toplanamaz. Benim ihtiyacım olan sayıları toplamaktı. Peki ben bu kısıtı nasıl verebilirim?

Generic Type Constraints

Generic type kullanırken tipimiz o kadar da genel olmasını istemiyorsak, belli özelliklere sahip tipler ile iş yapacaksak kısıtlama verme ihtiyacı duyarız. Aslında çok isteğe bağlı bir durum yok ortada. Kısıtlamak zorundayız çünkü o tipin o özelliğe sahip olup olmadığı bilmeden işlem yapamayız. Compiler ekranımıza hatayı fırlatır.

Örneğimiz için istediğimiz olay sayıları toplamaktı. O zaman generic tipimize kısıtımızı vermenin vakti geldi.

func sum<T: Numeric>(firstValue: T, secondValue: T) -> T {    firstValue + secondValue}

T tipimizin Numeric olacağını söylediğimizde aslında bunun toplanabilir bir değer olduğunu söyledik. Böylelikle numeric tipler üzerinde toplama işlemi yapan generic bir fonksiyon yazmış olduk.

Yahu ben bu generic tipleri sadece fonksiyonlarda mı tanımlayabilirim? Gelin bir class/struct yaratıp, class/struct’ta nasıl kullanacağımızı da görelim.

Generic Type Class/Struct

Generic class/struct’lar için Apple’ın kendi dokümanında vermiş olduğu örnek üzerinden ilerleyelim ve stack yapısını oluşturalım.

struct Stack<T> {    var items = [T]()    mutating func push(_ item: T) {        items.append(item)    }    mutating func pop() -> T {        return items.removeLast()    }}

Yine fonksiyonların adının sonuna tanımladığımız gibi burada da struct adından hemen sonra <> içine T tipinde olacağını veriyoruz. Bu T tipinde bir array’in olduğu ve yine T tipindeki değeri array’ın sonuna eklediğimizi, sonundan değerini sildiğimiz fonksiyonları tanımlıyoruz. Farkettiniz mi, fonksiyonlara tekrardan T tipinde olacağını tanımlamamıza gerek yok. Zaten scope’larında T tipi mevcut olduğu için buna gerek yok.

Stack içinde String, Int veya herhangi bir tipte değeri saklayabiliriz.

var names = Stack<String>()names.push("Bob")names.pop()

Anlaması kolay olması adına örnekleri basit tuttum. Peki gerçek hayatımızda bir iOS uygulama geliştirirken nerelerde kullanabileceğimiz hakkında birkaç örnek yapalım.

Verilen bir API’dan veri çekecek olalım. İçerisinde user verilerini barındırıyor. Bunun için bir yapı ve veriyi çekip yapımıza dönüştürecek bir fonksiyon yazalım.

struct User: Decodable {
var userId: Int?
let name: String?
let lastname: String?
let type: String?
}
func fetchUsers(completion: @escaping(Result<[User],ServiceError>) -> Void) { let url = URL(string: Constant.baseUrl + “users/”)!
var request = URLRequest(url: url)
request.httpMethod = HttpMethod.get.rawValue
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
guard
let data = data else {
completion(.failure(.noDataAvailable))
return
}
do {
let decoder = JSONDecoder()
let products = try decoder.decode([User].self, from: data)
completion(.success(products))
} catch {
completion(.failure(.canNotProcessData))
}
}
task.resume()
}

Burada dikkat etmemiz gerek kısım User tipi.

let products = try decoder.decode([User].self, from: data)

şeklinde gelen data’yı [User]’a decode ediyoruz. Her farklı modelimiz için yeni fetch fonksiyonları oluşturmak pek hoş olmayacaktır. 10 farklı modeliniz olsa bile tekrar eden bu kod blokları oldukça büyüyecek. Bunun yerine hayatımızı kurtaracak generic type’ları kullanalım.

func fetchData<T: Decodable>(type: T.Type, endPoint: String, completion: @escaping(Result<[T],ServiceError>) -> Void) {    let url = URL(string: Constant.baseUrl + endPoint)!
var request = URLRequest(url: url)
request.httpMethod = HttpMethod.get.rawValue
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
guard
let data = data else {
completion(.failure(.noDataAvailable))
return
}
do {
let decoder = JSONDecoder()
let result = try decoder.decode([T].self, from: data)
completion(.success(result))
} catch {
completion(.failure(.canNotProcessData))
}
}
task.resume()
}

Artık dışarıdan T tipinde bir değer alacağız ve bu T tipi için datayı decode edeceğiz. Her bir model için aynı fonksiyonu kullanabilirim. Burada dikkat edilmesi gereken T nin Decodable olmasıdır.

Umarım anlaşılır olmuştur. Yeni öğrenen biri için hemen aklına gelen çözüm generic type olmayabilir. Projemde nereyi generic yapabilirim diye endişelenmeyin. Karşılaştığımız kod tekrarı, kodu temiz yazma kaygısı bizi ister istemez zaten generic işler yapmaya itecek. 🚀

--

--