Swift Error Handling (Hata Yakalama)

Merhaba arkadaşlar, Swift dersleri serimize Swift Error Handling (Hata Yakalama) ile devam ediyoruz. Herhangi bir projede çalışırken proje içerisinde daha sonra hata çıkarabilecek noktaları, hata vermemesi üzere çözüme kavuşturmalıyız. Error Handling konusu bu aşamada imdadımıza yetişir ve projemizin crash olmasını engeller. Meydana gelen hatalar ile ilgili kullanıcıyı bilgilendirir veya kodun hata senaryosu ile devam etmesini sağlar.

Swift Error Handling

Error Handling kısmına hataları ne şekilde tutmamız gerektiği ile başlayalım. Aynı tip hataların bir arada tutulması mantıklı olacaktır. Bu sebeple benzer hataları aynı enum içersinde tutabiliriz. Daha sonra bu hataları,  enum vasıtasıyla çağırabiliriz.

enum LoginError: Error{
    case emptyUsername
    case emptyPassword
    case wrongUsername
    case wrongPassword
}

enum ATMError: Error{
    case wrongPin
    case noDeposit
    case overDayLimit
}

enum GeneralError: Error{
    case error
}

Hataları yukarıdaki örnekler gibi toplu halde tutabiliriz dedik. Peki bu hataları nasıl kullanabiliriz o kısma geçelim. Her zamanki gibi senaryomuzu oluşturalım; atm’den para çekerken karşılaşacağımız hatalar ile ilgili bir senaryomuz olsun ve bu senaryo üzerinden ilerlemeye devam edelim. Yukarıda belirlediğimiz üç adet hata tipi bizim için yeterli olacaktır. Bütün kodları aşağıya  sırasıyla bırakacağım dolayısıyla ilk önce view controller ile başlayalım:

class ErrorHandlingViewController: UIViewController {

    var deposit = 30
    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

View controller içinde deposit adında bir değişkenimiz var ve bizim banka hesabımızdaki para miktarımızı göstermektedir. Şimdi para çekmemize olanak veren bir metodu view controllera ekleyelim ve bunu hata fırlatmaya uygun halde yazalım:

func withdrawMoney(amount: Int) throws{
    if self.deposit < amount{
        throw ATMError.noDeposit
    }else{
        self.deposit -= amount
    }
}

Yukarıda bulunan withdrawMoney() metodumuz bir return değeri almıyor ve parametre aldığı parantezden hemen sonra throws anahtar kelimesini kullandık. Bu aşamada metodumuza hata fırlatabileceği bilgisini veriyoruz. Ardından hesabımızdaki bakiyeyi kontrol ediyoruz ve çekmek istediğimiz tutar hesabımızdaki tutardan fazla ise noDeposit hatasını fırlatıyoruz.

Hataları Yakalamak (Do-catch)

Farklı programlama dillerinde bulunan try – catch yapısına benzeyen bu yapı swift içinde do, try ve catch olarak kendine yer bulmuştur. Yukarıda hata fırlatmayı(throw) gördük ancak fırlattığımız bu hatayı yakalayıp, yakaladığımız duruma göre aksiyon almamız gerekir. İşte do, try ve catch anahtar kelimelerini hatayı yakalarken kullanmaktayız. Örnek üzerinden devam edelim:

do{
     try withDrawMoney(amount: 40)
     print("\(deposit)")
}catch{
     print("printed -> \(error) error")   // printed -> noDeposit error
}

Do – catch, if-else gibi iki bloktan oluşur. Do bloğunda try anahtar kelimesi ile yapılması gereken işlem yapılır. Bu işlem sonucunda bir hata fırlatılırsa, bu hata catch bloğunda yakalanacaktır. Yukarıdaki örneğimizde hesabımızda 30 birim bulunmaktaydı ve biz 40 birim çekmeye çalıştık. Metot gövdesinde bu durumu kontrol edip, böyle durumlarda noDeposit hatası fırlatacağımızı yazmıştık. Nitekim bu örneğimizde noDeposit hatası fırlattık ve catch bloğunda bu hatayı yakaladık. Bu hatayı print ettiğimizde enum içinde tanımladığımız şekilde bastıracaktır. Projenizin büyüdüğünü ve hata isimlerini karıştırabileceğimizi  veya aramak için zaman kaybedeceğimizi düşünelim; bu durumda şu şekilde bir extension yazabiliriz:

extension ATMError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .wrongPin:
            return NSLocalizedString(
                "Your pin code is wrong", comment: ""
            )
        case .noDeposit:
            return NSLocalizedString(
                "You have not enough money",
                comment: ""
            )
        case .overDayLimit:
            return NSLocalizedString(
                "Exceed the day limit",
                comment: ""
            )
        }
    }
}

Bu extension sayesinde hatamızın tanımını veya tam olarak hangi durumda bu hatayı fırlatacağımızı belirleyip bu hatayı bastırabiliriz. Extension dersimiz için buraya bakabilirsiniz.

do{
    try withDrawMoney(amount: 40)
    print("\(deposit)")
    
}catch{
    print(error.localizedDescription)   //  -> You have not enough money
}

Converting Errors to Optional (Hataları Optional Yapmak)

Bazı durumlarda hatanın tipine ihtiyacımız olmaz, hata olup olmadığı bizi ilgilendirir. Böyle durumlarda do-catch bloğunu yazmadan optional try ile problemi tek satırda çözebiliriz. Varsayalım withdrawMoney metodumuz bize kalan bakiyemizi döndürecek veya hata fırlatacak. O halde örneğimize bakalım:

func withDrawMoney(amount: Int) throws -> Int{
    if self.deposit < amount{
        throw GeneralError.error
    }else{
        self.deposit -= amount
        return self.deposit
    }
}

Güncellediğimiz metodumuz artık genel bir hata fırlatacak. Peki biz nasıl çağıracağız:

let result = try? withDrawMoney(amount: 20)
print(result)   -> Optional(10)
let result = try? withDrawMoney(amount: 40)
print(result)   -> nil

Hatanın tipinin bizi ilgilendirmediğini söylemiştik. Güncellediğimiz metodumuz artık genel bir error fırlatıyor. “result” parametremiz, try bloğu çalıştığında metot hata fırlatıyorsa nil olacaktır. Eğer metot hata fırlatmaz ise metodun return değeri result parametresine eşitlenecektir.

try!

Eğer try bloğunda gerçekleşecek işlemin hata fırlatmayacağından emin isek “try?” yerine “try!” kullanıp result parametresini optional olmaktan kurtarabiliriz.

let result = try! withDrawMoney(amount: 20)
print(result)   -> 10

if let and guard let

Bu iki kod parçası hata yakalamak için benim en çok kullandığım parametreler ve yaygın olarak kullanıldığını da görüyorum. Önceki derslerimizde bu konu anlatıldı ancak hata yakalama altında da bahsedilmeyi hak ediyor. if let bloğunda eşitliğin sol tarafındaki değişken veya sabite, eşitliğin sağ tarafındaki değişken, return değeri vb. atanmaya çalışılır. Eğer atanabiliyorsa if bloğuna girilir, atanamıyorsa else bloğuna girilir.

func ifDeneme () throws{
        if let result = try? withDrawMoney(amount: 20) {
            print(result) //  ->  10
        }else{
            throw GeneralError.error
        }
    }

Yukarıdaki ifDeneme() metodunda result parametresine eşitliğin diğer tarafındaki withDrawMoney() metodunun return değeri atanabilir çünkü return değeri 10’dur.

func ifDeneme2 () throws{
        if let result = try? withDrawMoney(amount: 40) {
            print(result) 
        }else{
            throw GeneralError.error
        }
    }

Yukarıdaki ifDeneme2() metodunda ise withdrawMoney() metodu error fırlatıyor, dolayısıyla result parametresi nil olacaktır ve general error fırlatacaktır.

Guard kullanımı da benzerdir:

    func guardDeneme () throws{
        guard let result = try? withDrawMoney(amount: 40) else{
            throw GeneralError.error
        }
    }

Yukarıdaki guardDeneme() metodunda ise result parametresine eşitliğin sağ tarafı atanmaya çalışılmaktadır. Atanabilirse result parametresi alınır, atanamazsa da general error fırlatılacaktır.

Özet

Bu dersimizde Swift Error Handling konusunu inceledik. En başta da belirttiğimiz gibi, hata alınabilecek blokları önceden belirleyip bir hata senaryosu çıkarmak gerekmektedir. Bu hatalara göre de farklı akışlara yönlenilebilir. Örneğin kullanıcı şifresi yanlış bir şekilde login olmaya çalıştığında bu şekilde hata fırlatıp, kullanıcıya hatalı şifre pop up view gösterilebilir. Bu şekilde örnekler artırılabilir. Dersi burada bitirirken herkese faydalı olmasını umarak, mutluluklar diliyorum. Soru, görüş ve önerilerinizi yorum kısmından veya sorucevap kısmından iletebilirsiniz. Sağlıcakla…

Tüm Swift derslerimiz için tıklayınız.

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

12

Ali Hasanoglu

Yorum Yaz

Haftalık Bülten

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