Swift Generic Nedir? Generic Kullanım Örnekleri Part 2

Merhaba arkadaşlar, Swift dersleri serimize Swift Generic Part 2 ile devam ediyoruz. İlk dersimize buradan erişebilirsiniz. Bu derste iş biraz daha karmaşıklaşacak dolayısıyla önce ilk dersi tekrarlayıp buradan devam etmek daha faydalı olacaktır. Bu dersimizde generic protokoller, associatedtype,  uygulamalarımızda kullanabileceğimiz temel bir generic network sınıfı gibi konuları ele alıyoruz. Hemen derse geçiyorum öyleyse:

Generic Protokoller ve associatedtype

Bir önceki Swift Generic dersimizde bir metot yazdığımızda bu metot içerisinde “==”, “!=”, “>=” vb. kontrol operatörlerini kullanabilmek için bazı protokolleri (Equatable, Comparable) generic tipimize eklememiz gerekmekteydi. Şimdi işi bir seviye ileriye götürelim ve metot içerisine gelen parametreleri toplayıp toplamı dönen metodu yazmaya çalışalım. Önce generic olmayan halin şu şekilde yazabileceğimizi görebiliriz:

func sum(x: Int, y: Int) -> Int{
   return x + y
}

Yukarıda yazdığımız metot içerisine iki tane Int değer alabiliyoruz. Bu işlemi generic yapmaya çalışalım:

func genericSum<T>(x:T, y:T) -> T{
    return x + y
}

Evet generic yaptık ancak bizim oluşturduğumuz generic T tipi “+” operatörünü tanımıyor dolayısıyla derleyici hata verecektir. Peki bu durumu nasıl çözebiliriz: Bunu çözmek için farklı yöntemler kullanılabilir. Biz burada kendi protokolümüzü üreterek devam edeceğiz. Şimdi bunu yapmak için ilk adım olan protokolümüzü tanımlayalım:

protocol FourOperations {
    static func +(lhs: Self, rhs: Self) -> Self
}

Toplama yapmamıza izin verecek “+” metodunu içeren protokolümüzü ekledik. Şimdi bu protokolü kullanabilmek için toplama yaparken kullanacağımız tiplere(Int, Double vb.) bu oluşturduğumuz “+” metodunu haber vermemiz gerekiyor. Hemen bunu nasıl yapacağımıza bakalım:

extension Int: FourOperations{}
extension Double: FourOperations{}

Bu tiplerden FourOperations protokolünü extend ederek bu işlemi yaptık. Artık Int ve Double tiplerimiz yeni oluşturduğumuz “+” operatörünü tanımaya başladı. O halde yukarıda  yazdığımız hata veren metodu şı şekilde güncellersek problem çözülmüş olur.

func genericSum<T: FourOperations>(x:T, y:T) -> T{
   return x + y 
}

Evet güncellediğimiz metodumuzdaki generic tipimiz olan T  artık FourOperations protokolünden kalıtım alıyor. FourOperations protokolünü şimdilik Int ve Double’a extend ettik dolayısıyla bu iki tip ile generic metodumuzu kullanabiliriz.

print(genericSum(x:2, y:3))         // 5
print(genericSum(x:2, y:3.14))      // 5.14
print(genericSum(x:2.3, y:3.14))    // 5.44

Not: FourOperations protokolünü, farklı tipler için extend ederek o tipler için de kullanılabilir hale getirebiliriz. Örnek: Float. Ayrıca dört işlemin diğer operatörlerini ve kontrol operatörlerini benzer mantıkla ekleyip bu operatörleri generic tipimiz ile kullanılabilir hale getirebiliriz.

associatedtype

Protokol içerisindeki metotlarda, generic tipleri kullanabilmek için “associatedtype” anahtar kelimesine ihtiyaç duyarız. Bu anahtar kelime ile tanımladığımız generic tipi, protokolü eklediğimiz sınıf vb. yapılarda belirlememiz gerekmektedir. Peki nasıl yapacağız, örnek ile açıklamaya çalışalım:

protocol AssociatedTypes{
    associatedtype T
    func printData(data: T)
}

extension AssociatedTypes{
    func printData(data: T){
        print(data)
    }
}

Şimdi yukarıda neler oldu hemen bakalım; öncelikle bir protokol tanımladık ve bu protokolümüzün generic bir T tipi alacağını belirledik. Protokolümüzü extend ederek gelen T tipindeki veriyi konsola yazdırdık. Peki yukarıda bahsettiğimiz hangi tipte geleceğini  nerede belirleyeceğiz. Hemen örneğini yazalım:

class Employer: AssociatedTypes{
    typealias T = String

    func printMyPosition(){
        self.printData(data: "I'm an employer")
    }
}

class Employee: AssociatedTypes{
    typealias T = Int

    func printMyWorkYears(){
        self.printData(data: 17)
    }
}

Yukarıda tanımladığımız sınıflar AssociatedTypes protokolümüzü conform etti ve içlerinde typealias anahtar kelimesini kullanarak T tipimizin aslında hangi tip olacağını belirledik. Örneklerde iki farklı tipte belirledik ancak sayıyı artırabiliriz.

Not: Eğer AssociatedTypes protokolü içine bir değişken eklemiş olsaydık ve bu değişken de protokol içinde belirlediğimiz generic tipte olur ise conform ettiğimiz sınıf vb. içerisinde “typealias” kullanarak tipi belirlemeye gerek kalmaz çünkü;  conform ettiğimiz sınıf protokol içine yazdığımız değişkenin tipini bizden isteyecektir. Örnek üzerinde bakalım:

protocol AssociatedTypes{
    associatedtype T
    
    var items: [T] {get set}
    func printData(data: T)
}

class Employer: AssociatedTypes{
    var items: [String] = []

    func printMyPosition(){
        self.printData(data: "I'm an employer")
    }
}

Protokol ve sınıfımızı güncellediğimizde “typealias” anahtar kelimesini kullanmaya gerek kalmadığı görülecektir.

Swift Generic Where Clauses

“where” anahtar kelimesi kullanımı bir önceki derste anlattığımız Type Constraintlere çok benzemektedir. Örneği verip üzerine konuşalım:

func compare<T>(lhs: T, rhs: T) -> T where T: Comparable{
    return lhs >= rhs ? lhs: rhs
}

Yukarıdaki where clause sayesinde generic T tipimizin, Comparable protokolünün operatörlerine erişme durumunu ayarladık. Farklı bir kullanımı ise şu şekildedir:

extension AssociatedTypes where T == String{
    func printData(data: T){
        print(data)
    }
}

Yukarıdaki kullanımında ise, gelen T tipinin ne olduğunu belirlememize olanak verir. Eğer T String ise bu extension kullanılacaktır. Farklı tipler için de extension eklenebilir.

Bonus Bölüm (Generic Network Calls)

Mobil uygulama geliştirirken uzak sunucudaki dataları alabilmek için sunucuya istek atıp gelen datayı uygulama içinde kullanabileceğimiz objelere çevirmemiz gerekmektedir. Bu işlemi generic hale getirerek, rahat anlaşılan ve zamandan tasarruf edebileceğimiz bir yapı oluşturabiliriz. İstek atabilmek için gerekli konfigürasyonlar vs. burada açıklamayacağız ancak soru olması durumunda yorumlardan sorabilirsiniz. Burada istek attığımız metot ve dönen datanın parse edilmesini generic olarak yapmak bizim hedef noktamız olacak. O halde istek attığımız metoda genel hatlarıyla bakalım:

    func request<T>(model: T.Type, requestType: RequestType, suffixStr: String, completion: @escaping (NetworkResponse<T>) -> ()) where T : Decodable{

        let endPoint = BASE_URL + requestType.rawValue + "=" + suffixStr        
        AF.request(endPoint,method:.get, encoding: URLEncoding.httpBody).responseData { [weak self] data in
            
            switch data.result{
            case let .success(responseData):
                self?.handleDataResponse(data: responseData, completion: completion)
            case .failure(_):
                completion(.httpFail(.unknown))
            }
        }
    }
private func handleDataResponse<T>(data: Data?, completion: @escaping (NetworkResponse<T>) -> ()) where T: Decodable {
       
        guard let data = data else { return completion(.httpFail(.unknown)) }
        
        do {
            let model = try JSONDecoder().decode(T.self, from: data)
            completion(.httpSuccess(model))
        }
        catch let error as NSError{
            print(error.userInfo)
        }
    }

Not: İstek atabilmek için Alamofire kütüphanesi kullanılmıştır.

Yukarıdaki metotlardan kısaca bahsedelim; ilk metot(request metodu) içerisine Decodable protokolü ile uyumlu çalışabilen bir T modeli almaktadır. Yukarıda bahsettiğimiz gibi sunucudan bize dönen data bu model ile parse edilebilir. Ayrıca request metodu yine T tipinde model dönebilecek bir callback almaktadır ve bu callback sunucudan cevap geldikten sonra, galen cevap parse edilerek T tipinde model oluşturulup, request’in çağrıldığı sınıfa bu modeli dönecektir. İkinci metot içinde ise gelen cevabın nil olmaması durumunda, datayı modelleyip, “completion(.httpSuccess(model))” bloğunda ise oluşan modeli callback ile isteğin atıldığı sınıfa gönderecektir. Şimdi isteğin atılıp modelin gönderildiği bir metot örneğine bakalım:

enum NetworkResponse<T> {
    case httpSuccess(T)
    case httpFail(NetworkError)
}

enum NetworkError {
    case unknown
    case noData
}
func getMovieDetail(with id: String){
        requester?.request(model: MovieDetailModel.self, requestType: .detail, suffixStr: id, completion: { [weak self] (response) in
            switch response{
            case let .httpFail(error):
                print(error)
                self?.displayAlert(title: "Attention!", message: "Unexpected error occurred")
            case let .httpSuccess(movie):
                DispatchQueue.main.async {
                    let sb = UIStoryboard(name: "Main", bundle: nil)
                    if let vc = sb.instantiateViewController(withIdentifier: "MovieDetailViewController") as? MovieDetailViewController{
                        vc.movie = movie
                        self?.navigationController?.pushViewController(vc, animated: true)
                    }
                }
            }
        })
    }

Buradaki metot içerisinde ise parse edilmesini istediğimiz modeli request motodu içerisinde gönderiyoruz(MovieDetailModel). Callback içerisinde ise fail olma durumu ve success olma durumuna göre aksiyon alındı. Detay sınıfında MovieDetailModel nesnesi ile işlemlerimizi yapacağız. Bu projenin tamamına şahsi github hesabımdan erişip daha detaylı inceleyebilirsiniz.

Özet

İlk derse anlattığımız temel konuları bu derste daha da ilerletip generic protokollerden, where clauses’tan ve en son da network çağrılarımızı generic olarak modelleyebileceğimiz bir network yapısı kurduk. Swift Generic yapılar farklı tipler için tek bir metot ile işimizi yapmaya olanak verdiği için bizi code duplication ( aynı kodun farklı tipler için tekrar yazılması)  gibi problemlerden kurtardığı gibi, daha sade kod yazmamıza da imkan verir. Dersi burada bitirirken herkese faydalı olmasını umarak, mutluluklar diliyorum. Soru, görüş ve önerilerinizi yorum kısmından veya soru-cevap kısmından iletebilirsiniz. Sağlıcakla…

Generic network call projesinin tamamı: https://github.com/hasanogliali/MovieApp

Kaynak: https://docs.swift.org/swift-book/LanguageGuide/Generic.html

 

36

Ali Hasanoglu

3 Yorum

Haftalık Bülten

Mobilhanem'de yayınlanan dersleri haftalık mail almak ister misiniz?