iOS Mülakat Soruları

Selamlar herkese! iOS kod geliştirme sürecimde karşılaştığım önemli noktaları kaydetmek amacıyla bir not defteri tutmaya başladım. Ancak zamanla, bu notların aslında mülakatlarda sorulabilecek potansiyel sorular olduğunu fark ettim. Bu düşünceden hareketle, neden bu değerli bilgileri adım adım bloguma taşımayayım diye düşündüm. Şimdi sizlerle, mükemmel ötesi soruları ve üzerinde titizlikle düşündüğüm cevapları paylaşacağım.

Not: İlk 20-30 soruyu blogumda işledikten sonra, konuları daha sistemli bir şekilde kategorilere ayırarak farklı yazılarda toplamayı planlıyorum. Bu şekilde, bilgiye ulaşımı daha da kolaylaştırmış olacağım.

Her bir soru özelinde ilerleyen zaman dilimlerinde Github’da gistler oluşturarak parça parça anlatmaya çalışacağım her şeyi. Şuan cevaplara bakıyorsanız da çok takmayın. İleride üstünden hepsinin tek tek geçip eksik boşlukları dolduracağım ya da fazlalıkları atacağım. Şuan soru havuzu oluşturmaya çalışıyorum.

Swift

1. Swift programlama dilinde, static anahtar kelimesi ile tanımlanmış bir metod ile class anahtar kelimesi ile tanımlanmış bir metod arasındaki fark nedir? Hangi durumlarda class anahtar kelimesi kullanmak daha avantajlı olur?

Swift’te, static anahtar kelimesi ile tanımlanan metodlar, sınıfa ait olup nesne örneği oluşturulmadan doğrudan sınıf üzerinden çağrılabilir. Bu metodlar, sınıfın alt sınıfları tarafından override edilemezler, bu da onları sabit bir fonksiyonaliteye sahip yapar.

Buna karşın, class anahtar kelimesi ile tanımlanan metodlar da sınıfa ait olup nesne örneği oluşturulmadan doğrudan sınıf üzerinden çağrılabilirler. Ancak, bu metodlar alt sınıflar tarafından override edilebilirler. Bu özellik, metodun farklı sınıflar içinde farklı davranışlar sergilemesine izin verir, böylece polymorphism özelliğinden faydalanılmasını sağlar.

class anahtar kelimesinin kullanımı özellikle, bir metodun farklı sınıflarda farklı implementasyonlara sahip olması gerektiğinde avantajlıdır. Örneğin, bir sınıf hiyerarşisi içinde farklı sınıfların benzer bir metod üzerinde kendi spesifik işlemlerini gerçekleştirmesi gerektiğinde class metodları tercih edilir. Bu sayede kod tekrarı azaltılır ve kodun genişletilebilirliği artırılır.

2. Swift’te struct yapılarında kullanılan let ve var anahtar kelimeleri arasındaki farklar nelerdir? Özellikle, tüm özellikleri let ile tanımlanmış bir struct‘ın oluşturulması sürecini var ile tanımlanmış bir struct ile karşılaştırarak açıklayınız.

Cevap: Swift’te let ve var, struct içerisindeki özelliklerin (properties) sabit (immutable) ya da değişken (mutable) olmasını belirler. let anahtar kelimesi kullanılarak tanımlanan özellikler sabittir ve bu özelliklere atanmış değerler, struct’ın oluşturulduğu anda verilmeli ve sonradan değiştirilemez. Özellikler let ile tanımlandığında, bu özelliklerin başlangıç değerleri mutlaka belirtilmelidir. Aksi takdirde, compiler hata verecektir.

var ile tanımlanan özellikler ise değişebilirdir. Bu, özelliklerin değerlerinin sonradan değiştirilebileceği anlamına gelir. Eğer özellikler optional olarak tanımlandıysa (var name: String? gibi), Swift otomatik olarak nil değeri atar ve struct örneği başlangıç değerleri belirtilmeden oluşturulabilir. Bu durum, daha esnek bir yapı oluşturmanıza olanak sağlar çünkü değerlerin başlangıçta atanmasına gerek kalmaz.

3. Swift’de protokollerin önemi nedir ve bu protokolleri kullanmanın avantajları nelerdir?

  • Tip Güvenliği: Bir protokolü uygulayan tüm tipler, protokolde tanımlanan metodları ve özellikleri sağlamalıdır, bu da tip güvenliği sağlar.
  • Esneklik ve Yeniden Kullanılabilirlik: Farklı tipler, aynı protokolü uygulayarak ortak bir arayüz paylaşabilir. Bu, kodun yeniden kullanılabilirliğini ve esnekliğini artırır.
  • Soyutlama: Protokoller, bir nesnenin ne yapması gerektiğini tanımlar fakat nasıl yapılacağını tanımlamaz. Bu, daha temiz ve daha yönetilebilir kod yapısına olanak tanır.
  • Bağımsızlık: Protokoller, bağlantı noktaları olarak işlev görerek, sistemler arası bağımlılıkları azaltır. Bu, özellikle büyük ve modüler projelerde faydalıdır.
  • Test Edilebilirlik: Protokolleri kullanarak, birim testleri sırasında sahte nesneler oluşturulabilir, bu da test sürecini kolaylaştırır.

4. Builder pattern nedir ve hangi durumlarda kullanılır?

Builder pattern, karmaşık nesnelerin oluşturulma sürecini basitleştirmek için kullanılır. Bu pattern, nesne oluşturma kodunu, temsil edilecek nesnenin gerçek kullanımından ayırır. Özellikle, bir nesnenin birden çok bileşeni veya çok sayıda konfigürasyonu olduğunda kullanışlıdır. Builder pattern, nesneyi adım adım inşa etmeyi ve son yapılandırma aşamasına kadar değişiklik yapmayı kolaylaştırır. Örneğin, karmaşık bir belge düzenleyici veya UI kontrol yapılandırması oluşturulurken kullanılabilir. Bu yapı, kod tekrarını azaltmaya ve kodun bakımını kolaylaştırmaya yardımcı olur.

5. Swift dilinde operator overload etmek iOS programlamada nasıl işimize yarayabilir?

Swift’te operator overloading, iOS programlamada özellikle özelleştirilmiş veri tipleri üzerinde matematiksel ve lojik işlemler yaparken kodun daha temiz ve anlaşılır olmasını sağlar. Örneğin, geometrik hesaplamalar veya animasyonlar gibi alanlarda, geliştiricilerin daha basit ve etkili kod yazmalarına olanak tanıyarak, performans optimizasyonu ve kullanıcı deneyiminin geliştirilmesine katkıda bulunur. Bu, uygulamaların bakımını ve geliştirilmesini kolaylaştırarak, iOS uygulama geliştiricileri için büyük bir avantaj sağlar. Örneğin iki CGPoint’in toplanma işlemi:

func +(left: CGPoint, right: CGPoint) -> CGPoint {
    return CGPoint(x: left.x + right.x, y: left.y + right.y)
}

6. Bir UILabel metnini kullanıcının girişine bağlı olarak güncellemek için, property observer (willSet veya didSet) kullanmak mı yoksa bir metod mu tercih edersiniz? Seçiminizin nedenini kısaca açıklayın.

Özel bir metod kullanılması genellikle tercih edilir çünkü metodlar işlevlerini açıkça tanımlar, bu da kodun daha anlaşılır ve bakımının daha kolay olmasını sağlar. Ayrıca, metodlar üzerinde unit testler yapmak daha doğrudandır, böylece hataları tespit etmek ve düzeltmek daha kolay hale gelir. Metodlar yan etkileri merkezi bir noktada toplar, bu da uygulamanın genel davranışını daha öngörülebilir ve yönetilebilir kılar. Büyük projelerde willSet ve didSet kullanımı, özellikle birçok observer birbirini tetiklediğinde, kodun karmaşıklığını artırabilir ve yan etkileri yönetmeyi zorlaştırabilir. Bu nedenle, büyük ölçekli sistemlerde observer yerine metod kullanmak, uygulamanın performansını ve sürdürülebilirliğini artırma açısından daha etkilidir.

7. Swift dilinde oluşturduğunuz bir string içerisinde “Ömer \ Faruk” ifadesini yazdırmak istiyorsunuz, ancak kodunuzu çalıştırdığınızda “Invalid escape sequence in literal” hatası alıyorsunuz. Bu sorunu nasıl çözersiniz?

Swift’te \ karakteri özel bir escape karakteridir. Bu karakteri string içinde kullanmak için iki yöntem vardır:

  • Escape karakteri olarak \\ kullanmak: “Ömer \\ Faruk”
  • Raw string kullanımı: #”Ömer \ Faruk”#

8. #error derleyici yönergesi ne işe yarar ve nerede kullanabiliriz?

#error derleyici yönergesi, belirttiğimiz bir mesajla derleyicinin bir hata mesajı üretmesini zorlar. Kodumuzun tvOS ile uyumlu olmadığını belirtmek için işletim sistemi kontrolü yanında #error kullanabiliriz.

9. “Swift dilinde, ‘flatMap’ fonksiyonu Swift 4.1 itibarıyla ‘compactMap’ olarak adlandırılmaya başlandı. Bu değişiklik, nil döndürebilen değerlerin diziden çıkarılması işlemini daha anlaşılır kılmak içindir. Eğer eski ve yeni Swift sürümleri için uyumlu bir kod yazmanız gerekseydi, bu durumu nasıl ele alırdınız? Bir örnek kod parçası ile açıklayınız.”

Bu durumu ele almak için, Swift’in koşullu derleme ifadeleri kullanılabilir. #if swift(>=4.1) ve #else derleyici direktifleri ile farklı Swift sürümlerinde farklı fonksiyonlar kullanılabilir. Bu, kodun hem eski hem de yeni sürümlerle uyumlu çalışmasını sağlar.

10. assert() işlevi ne işe yarar?

Swift’te assert() fonksiyonu, belirtilen koşulun doğru olduğunu doğrulamak için kullanılır. Koşul yanlışsa, program durdurulur ve hata mesajı görüntülenir. Bu, geliştirme sürecinde hataları erken fark etmek ve düzeltmek için kullanışlıdır.

var age = 15
assert(age >= 18, "Yaşınız 18'den küçük olamaz")

11. Swift programlama dilinde canImport() direktifini kullanarak iOS için SwiftUI ve UIKit, macOS için ise AppKit kütüphanelerinin platforma özel olarak nasıl yönetileceğini açıklayınız. Kullanılacak direktiflerin seçim kriterlerini ve bu direktiflerin projenin platformlar arası uyumluluğunu nasıl etkilediğini de belirtiniz.

Swift’te, canImport() direktifi, belirli bir modülün veya kütüphanenin bulunduğunuz platformda kullanılabilir olup olmadığını kontrol eder. os() koşulu ise belirli bir işletim sistemi için kodun derlenip derlenmemesi gerektiğini belirler. Bu özellikler, farklı platformlar için uyumlu ve verimli uygulamalar geliştirilmesine olanak tanır.

iOS için SwiftUI ve UIKit kütüphanelerini yalnızca iOS platformunda kullanabilmek için şu şekilde bir yönetim yapılır:

#if canImport(SwiftUI) && os(iOS)
import SwiftUI
#endif

#if canImport(UIKit) && os(iOS)
import UIKit
#endif

#if canImport(AppKit) && os(macOS)
import AppKit
#endif

Sonuç olarak, canImport() ve os() kullanımı, platformlar arası uygulama geliştirme sürecinde büyük önem taşır. Bu direktifler sayesinde, kodun doğru platformda, doğru şekilde derlenmesi sağlanarak, uygulamaların performansı ve kaynak kullanımı optimize edilir.

12. Swift’de CaseIterable protokolünü kullanmanın avantajları nelerdir ve hangi durumlarda kullanılması uygundur?

Swift’de CaseIterable protokolü, bir enum türünün tüm vakalarını bir koleksiyon olarak listelemek için kullanılır. Bu protokolü bir enum’a uyguladığınızda, otomatik olarak .allCases özelliği eklenir ve bu da enum içinde tanımlanmış tüm vakaları bir dizi olarak döndürür. Bu özellik, özellikle UI bileşenlerinde seçeneklerin dinamik olarak listelenmesi gerektiğinde veya testler sırasında enum vakalarının eksiksiz olarak test edilmesi gerektiğinde büyük kolaylık sağlar. Örneğin, bir formda kullanıcıya sunulan seçimler bir enum tarafından temsil ediliyorsa, CaseIterable kullanarak tüm seçimleri kolayca ve hatasız bir şekilde UI’a aktarabilirsiniz. Ayrıca, enum vakaları üzerinde iterasyon yapılması gereken her durumda, bu protokol kod tekrarını önler ve enum’un vakalarını yönetmeyi basitleştirir. Dolayısıyla, enum’larınızın potansiyel olarak genişleyebileceği ve vaka listesinin dinamik olarak kullanılması gerektiği durumlarda CaseIterable çok işlevsel bir çözüm sunar.

13. Swift’de final anahtar kelimesi ne amaçla kullanılır ve bu kullanımın sınıflar veya metotlar üzerindeki etkileri nelerdir?

Swift programlama dilinde final anahtar kelimesi, bir sınıfın veya sınıf üyelerinin (metotlar, özellikler, indeksleyiciler) miras alınmasını veya üzerine yazılmasını engellemek için kullanılır. Özellikle, bir sınıf final olarak işaretlendiğinde, bu sınıf başka hiçbir sınıf tarafından türetilmez. Benzer şekilde, bir metot veya özellik final olarak işaretlendiğinde, bu metot veya özellik türetilmiş sınıflarda değiştirilemez. Bu kullanım, yazılımın mimarisini güçlendirir, yanlışlıkla yapılabilecek değişikliklerin önüne geçer ve kodun beklenen davranışlarının korunmasını sağlar. Ayrıca, final anahtar kelimesi performans optimizasyonu açısından da faydalıdır. Çünkü Swift derleyicisi final ile işaretlenmiş sınıf ve üyeleri için daha optimize edilmiş kod üretebilir, dinamik yönlendirme ihtiyacını ortadan kaldırır. Bu nedenle, final kullanımı hem güvenlik hem de performans için önemli bir araç olarak karşımıza çıkar.

14. Swift’de if let, guard let ve guard case kullanımlarını karşılaştırarak, her birinin ne zaman ve neden tercih edilmesi gerektiğini açıklayın.

Swift’te if let, guard let, ve guard case yapıları optional değerler ve enum vakalarıyla çalışırken farklı durumlar için tercih edilir. If let kullanımı, bir optional değerin null olup olmadığını kontrol edip, değer varsa belirli bir kod bloğunu çalıştırmak için idealdir. Ancak bu, kodun derinlemesine yuvalanmasına sebep olabilir. Buna karşın, guard let yapısı, optional bir değeri unwrap eder ve değer yoksa erken çıkış yapar, bu sayede kod daha düz bir yapıda kalır. Guard case ise enum vakalarını kontrol etmek için kullanılır; belirli bir vaka ile eşleşme durumunda kod bloğunu çalıştırır, aksi takdirde erken çıkış yapar. If let genellikle daha lokal, kısa süreli kontroller için, guard let ve guard case ise fonksiyonun başında, gerekli koşullar sağlanmadığında hemen çıkış yapmak ve kodun geri kalanını güvenle çalıştırmak için kullanılır. Bu yapılar, Swift kodunun güvenliği ve okunabilirliği açısından önemli katkılar sağlar.

15. Swift dilinde kullanılan try, try? ve try! anahtar kelimeleri arasındaki farklar nelerdir?

Swift’te hata yönetimi, fonksiyonların ve metodların hata fırlatabilme kapasitesine dayanır. Bu hataları ele almak için try, try? ve try! kullanılır. try anahtar kelimesi, bir hata fırlatma potansiyeli olan kodu işaretler ve bu kodu do-catch blokları içinde kullanılır. Hata fırlatılırsa, catch bloğu hataları ele alır. try? ise farklı bir yaklaşım sunar; eğer bir hata fırlatılırsa, sonuç nil olarak döner ve bu şekilde hata sessizce işlenmiş olur. Bu yöntem, hata yönetimini zorunlu kılmaz ve kodun akışını kesintiye uğratmaz. try! kullanımı ise, kodun hiçbir zaman hata fırlatmayacağını iddia eder. Eğer hata fırlatılırsa, bu durumda program çöker. Bu nedenle try! kullanımı risklidir ve sadece hata fırlatılmasının kesinlikle mümkün olmadığı durumlardan emin olduğunuzda kullanılmalıdır. Her üç durum da Swift’in hata yönetiminde esneklik sağlar ve farklı senaryolara uygun çözümler sunar.

16. Swift’te @propertyWrapper kullanarak, bir User sınıfının email özelliği için basit bir e-posta doğrulama mantığı nasıl uygulanabilir? Bu doğrulama, atanan e-posta adresinin geçerli bir e-posta formatına sahip olup olmadığını kontrol etmelidir.

Bir @propertyWrapper kullanarak, User sınıfındaki email özelliğine atanan değeri yakalayabilir ve bu değerin geçerli bir e-posta formatına sahip olup olmadığını kontrol edebiliriz. Eğer değer geçerli bir e-posta formatında değilse, özelliği boş bir string ile başlatabiliriz veya bir hata mesajı saklayabiliriz.

@propertyWrapper
struct ValidEmail {
    private var value: String
    private let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
    
    var wrappedValue: String {
        get { value }
        set {
            if isValidEmail(newValue) {
                value = newValue
            } else {
                value = ""
                print("Invalid email format.")
            }
        }
    }

    init(wrappedValue initialValue: String) {
        if isValidEmail(initialValue) {
            self.value = initialValue
        } else {
            self.value = ""
            print("Invalid email format.")
        }
    }
    
    private func isValidEmail(_ string: String) -> Bool {
        let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegex)
        return emailPred.evaluate(with: string)
    }
}

struct User {
    @ValidEmail var email: String
}

var user = User(email: "example@example.com") // Geçerli e-posta
print(user.email)  // Çıktı: example@example.com

var user2 = User(email: "example.com") // Geçersiz e-posta
print(user2.email)  // Çıktı: 

Bu kod, @ValidEmail property wrapper’ını kullanarak bir e-posta adresinin doğru formatta olup olmadığını kontrol eder. E-posta geçersizse, özelliği boş bir string olarak saklar ve konsola bir hata mesajı yazdırır. Bu, form doğrulama gibi işlemleri kolayca yönetmek için etkili bir yöntemdir.

17. Yeni başlayan bir iOS geliştiricisine “Closure” yapısını nasıl anlatırsınız?

Swift dilinde closure’lar, adları olmayan fonksiyonlardır ve çoğu zaman bir değişken içerisine atanarak ya da bir fonksiyona parametre olarak verilerek kullanılır. İşlevsellikleri birçok yönden normal fonksiyonlara benzer; parametre alabilir, değer döndürebilir ve çevrelerindeki kapsamda bulunan değişkenlere erişebilirler. Closure’lar, Swift’in fonksiyonel programlama özelliklerinden birini temsil eder ve özellikle asenkron işlemler, callback fonksiyonları gibi durumlar için çok kullanışlıdırlar.

Closure’lar aynı zamanda kapsamlı değer yakalama (capturing values) özelliğine sahiptir. Bu, bir closure’ın tanımlandığı kapsamdaki değişkenlere ve sabitlere erişebilmesi anlamına gelir. Bu özellik, asenkron işlemlerde veya bir işlemin sonucunu daha sonra kullanmak üzere saklamak istediğinizde oldukça faydalıdır.

Closure’ların kullanımı, kodunuzu daha temiz, okunabilir ve yeniden kullanılabilir hale getirebilir. Ancak, closure’lar özellikle büyük ve karmaşık projelerde dikkatli kullanılmalıdır. Bellek sızıntılarına (memory leaks) ve istenmeyen referans döngülerine (retain cycles) sebep olabilirler, özellikle self anahtar kelimesi closure içerisinde kullanıldığında. Bu nedenle, bu tür durumlarda genellikle [weak self] veya [unowned self] gibi zayıf veya sahipsiz referanslar kullanılması önerilir.

18. Swift dilinde “Generic” bir yapı neden önemli ve ne amaçla kullanılmalıdır?

Swift’te generic yapılar, çeşitli veri türleriyle uyumlu, tekrar kullanılabilir fonksiyonlar ve tipler oluşturarak kod tekrarını azaltır, tip güvenliği sağlar ve yazılım geliştirme sürecini kolaylaştırır. Bu özellik, aynı kodun farklı tiplerle çalışabilmesini sağlayarak, esneklik ve performans avantajları sunar.

19. Swift dilindeki multi-pattern catch ifadelerini açıklayabilir misiniz? Bu yaklaşımın hangi durumlarda yararlı olduğunu örneklerle anlatır mısınız?

Swift’te, hata yakalama işlemleri için do-catch blokları kullanılır. Multi-pattern catch ifadeleri, birden fazla hata türünü tek bir catch bloğunda yakalamak için kullanılır. Bu, kod tekrarını azaltır ve daha temiz bir yapı sağlar. Ayrıca, farklı hata türlerinin benzer şekilde ele alınmasını kolaylaştırır.

Örneğin, bir ağ isteği yapılırken çeşitli hatalar meydana gelebilir: bağlantı hatası, veri alınamaması veya yanıtın bozuk olması gibi. Bu hataların her biri farklı bir hata türü olabilir. Ancak, kullanıcıya gösterilecek sonuç aynı olabilir (örneğin, bir hata mesajı göstermek). Bu durumda, multi-pattern catch ifadeleri kullanılabilir:

enum NetworkError: Error {
    case connectionError
    case dataNotFound
    case corruptedData
}

func fetchData() {
    do {
        // Ağ isteğini burada yap
        throw NetworkError.dataNotFound  // Örnek hata durumu
    } catch NetworkError.connectionError, NetworkError.dataNotFound {
        // Bağlantı hatası veya veri bulunamadı durumlarını burada ele al
        print("Veri alınamadı veya bağlantı hatası.")
    } catch NetworkError.corruptedData {
        // Veri bozuk durumunu burada ele al
        print("Alınan veriler bozuk.")
    } catch {
        // Diğer tüm hatalar
        print("Bir hata oluştu.")
    }
}

Bu yapı, catch bloğunu daha verimli kullanmamızı sağlar. Aynı türde işlem yapılan hatalar tek bir catch bloğunda toplanabilir. Bu da hem kodun okunabilirliğini artırır hem de hata yönetimini merkezileştirir, böylece hata yönetimi daha tutarlı hale gelir. Özellikle büyük ve karmaşık projelerde, hata türlerine göre ayrı ayrı işlem yapmak yerine benzer hataları gruplandırarak işlemek, kodun sürdürülebilirliği açısından büyük önem taşır.

20. #available syntax ne işe yarar?

Swift’te #available sözdizimi, belirli bir API’nin veya işletim sistemi özelliklerinin kullanılabilirliğini kontrol etmek için kullanılır. Bu, uygulamanızın farklı platform sürümleri üzerinde çalışabilmesini sağlayarak, eski sürümlerle uyumluluğu korumanıza ve yeni özellikleri güvenle kullanmanıza olanak tanır.

if #available(iOS 10, *) {
    // iOS 10 ve üstü sürümlerde kullanılabilir özellikler
    someNewFunctionality()
} else {
    // Daha eski iOS sürümleri için alternatif bir çözüm
    someFallbackFunctionality()
}

Swift’te #available sözdizimi, uygulamanızın farklı iOS ve diğer Apple platform sürümleriyle uyumlu çalışmasını sağlayarak, yeni sürümlerde sunulan API’leri güvenle kullanmanıza olanak tanır. Bu sayede, yeni özellikleri kademeli olarak entegre edebilir ve eski sürümler için alternatif çözümler sunabilirsiniz, bu da uygulamanızın daha geniş bir kullanıcı kitlesi tarafından sorunsuzca kullanılmasını sağlar. Ayrıca, #available ile çoklu platform desteği sağlamak daha kolay ve güvenli hale gelir, kod yapınızı temiz tutarak hata yapma riskini azaltır ve bakım süreçlerini kolaylaştırır, böylece kullanıcı memnuniyetini ve uygulamanızın rekabet gücünü artırır.

21. Swift dilinde variadic fonksiyonların kullanımı nedir ve bu fonksiyonları neden tercih etmek isteyebilirsiniz? Basit bir variadic fonksiyon örneği yazınız.

Swift’te variadic fonksiyonlar, bir fonksiyona birden fazla parametre gönderilmesine izin verir. Bu parametreler fonksiyona aynı türde ve teorik olarak sınırsız sayıda değer olarak geçirilebilir. Variadic fonksiyonları kullanmanın avantajları arasında kod tekrarını azaltmak, fonksiyonların esnekliğini artırmak ve çeşitli senaryolarda fonksiyonların daha genel amaçlı hale gelmesi yer alır.

func calculateAverage(_ numbers: Double...) -> Double {
    let total = numbers.reduce(0, +)
    return numbers.isEmpty ? 0 : total / Double(numbers.count)
}

// Fonksiyonu test etme
let average = calculateAverage(10, 20, 30, 40, 50)
print("Ortalama: \(average)")  // Çıktı: "Ortalama: 30.0"

Bu fonksiyon, sıfır veya daha fazla Double türündeki sayıyı parametre olarak alır, bu sayıların toplamını hesaplar ve sayıların sayısına bölerek ortalamayı bulur. Fonksiyon, sayılar dizisi boş olduğunda sıfır döner, bu durum hatasız bir işleyiş sağlar. Bu özellikle, fonksiyonun çeşitli girdi senaryolarında dayanıklı olmasını sağlar.

22. Weak ve unowned arasındaki fark nedir?

Swift’te weak ve unowned anahtar kelimeleri, döngüsel referansları önlemek için kullanılır, ancak aralarında önemli farklar vardır. weak referansı, referans ettiği nesne bellekten atıldığında nil değerine dönüşür ve bu yüzden genellikle değişkenlerle kullanılır. weak kullanımı daha güvenli çünkü nesne yok olduğunda otomatik olarak nil olur ve runtime hatalarından kaçınmış oluruz. unowned ise referans ettiği nesne bellekten atıldıktan sonra nil değerine dönüşmez, dolayısıyla daha riskli olabilir. unowned kullanımı genellikle nesne ömrünün kullanıldığı kapsamı aşmayacağı durumlarda tercih edilir. Örneğin, bir sınıfın başka bir sınıfa olan bağımlılığı, ilk sınıfın var olduğu süre boyunca devam edecekse, unowned kullanmak uygun olabilir. Bu durumlar genellikle delegate veya closure (blok) yapılarıyla karşımıza çıkar.

23. escaping ve non-escaping closure’lar arasındaki temel farklar nelerdir?

Swift dilinde closure’lar, fonksiyonların parametreleri olarak geçebilen ve bir fonksiyonun kapsamı dışında da saklanabilen bloklardır. Bu closure’lar escaping ve non-escaping olmak üzere iki kategoriye ayrılır. Bu ayrım, closure’ların fonksiyonlar tarafından nasıl kullanıldığı ve yönetildiği ile ilgili önemli farklar yaratır.

Non-escaping closure’lar fonksiyonun çağrıldığı kapsam içinde başlar ve biter. Bunlar fonksiyon bitene kadar geçerli olur ve fonksiyonun dönüşünden sonra erişilemezler. Swift, non-escaping closure’ları varsayılan olarak kabul eder, bu da bellek yönetimi ve performans açısından faydalar sağlar. Fonksiyon bitiminde hemen hafızadan temizlendikleri için ekstra bellek yönetimi gerektirmezler ve daha hızlı çalışırlar. Tipik olarak, basit döngülerde veya hesaplama işlemlerinde kullanılırlar.

Diğer taraftan, escaping closure’lar, fonksiyonun dönüşünden sonra da yaşamaya devam edebilirler. Bu, özellikle asenkron işlemler, veri yükleme işlemleri veya uzun süreli işlemler için gerekli olan bir özelliktir. escaping closure’lar, @escaping etiketi ile işaretlenir ve genellikle dış kapsamlardaki değişkenlere erişim sağlamak ya da daha sonra çalıştırılmak üzere kaydedilmek üzere tasarlanır. escaping closure kullanırken, özellikle bellek sızıntılarını önlemek için self gibi değerleri [weak self] ya da [unowned self] ile yakalamak önem taşır. Bu yaklaşım, potansiyel hafıza sızıntılarını önler ve uygulamanın daha stabil çalışmasını sağlar.

Bu iki tip closure arasındaki temel farklar, Swift’in bellek yönetimine ve performans optimizasyonuna olan etkisinden kaynaklanır. non-escaping closure’lar genellikle daha basit ve hızlı çalışırken, escaping closure’lar daha esnek ve güçlü olabilir ancak doğru şekilde yönetilmezlerse bellek sorunlarına yol açabilir. Bu yüzden, doğru tip closure’ı kullanmak, uygulamanın gereksinimlerine ve beklenen davranışlarına göre önem taşır.

24. “Swift’te protokoller ve extensionlar nasıl birlikte kullanılır? Bir protokolde metod tanımlamak ile bir protokol extensionında metod tanımlamanın arasındaki farklar nelerdir ve bu kombinasyonun programınıza sağladığı avantajlar nelerdir?”

Swift’te, protokoller ve extensionlar genellikle birlikte kullanılarak programlara modülerlik ve esneklik kazandırılır. Protokoller, bir türün uygulaması gereken metot ve özelliklerin tanımlarını içerir. Bu, protokolü uygulayan türlerin bu metodları ve özellikleri sağlaması gerektiği anlamına gelir; ancak protokolde metodun nasıl gerçekleştirileceği (body) tanımlanmaz.

Buna karşın, bir protokol için extension kullanıldığında, bu extensionda tanımlanan metodlar tüm protokolü uygulayan türler için varsayılan implementasyonlar olarak işlev görür. Yani, protokolü uygulayan türler bu metotları kendi içlerinde tekrar tanımlamak zorunda kalmaz, direkt olarak extension içinde verilen implementasyonu kullanabilirler. Ancak, türler bu varsayılan davranışı özelleştirmek istediklerinde, kendi özel implementasyonlarını tanımlayarak extensiondaki varsayılan implementasyonu override edebilirler.

Bu yaklaşımın avantajları arasında, kod yeniden kullanımının artması ve kodun sürdürülebilirliği yer alır. Özellikle büyük projelerde, extensionlar sayesinde protokolleri uygulayan türlerin davranışlarını merkezi bir noktadan yönetmek ve güncellemek mümkün olur. Ayrıca, kodun genişletilmesi ve yönetilmesi daha kolay hale gelir çünkü yeni türler eklerken veya mevcutları değiştirirken, tüm değişiklikler tek bir yerde, yani extensionda yönetilebilir. Bu, büyük ve karmaşık yazılım projelerinde özellikle faydalıdır, zira tüm türlere uygulanan değişiklikler tek bir noktadan kontrol edilebilir ve güncellenebilir. Ayrıca, bu yöntem sayesinde kodun okunabilirliği ve bakımı kolaylaşır, çünkü ortak davranışlar merkezi bir konumda toplanır ve her tür için tekrar tekrar yazılması gerekmez.

25. Swift’de defer anahtar sözcüğünü ne zaman kullanırsınız?

Swift dilinde defer anahtar kelimesi, bir iş parçasının fonksiyon sonlandığında gerçekleşmesini sağlamak için kullanılır. Bu yapı, diğer dillerdeki try/finally bloklarına benzer şekilde çalışır. defer bloğu içine yazılan kodlar, içinde bulunduğu kapsayıcı fonksiyon sona ermeden hemen önce çalıştırılır. Bu, özellikle kaynakların temizlenmesi veya gerekli son işlemlerin yapılması gibi durumlar için oldukça faydalıdır.

Örneğin, bir dosyaya veri yazma işlemi gerçekleştirildiğinde, işlemin başarılı bir şekilde tamamlanıp tamamlanmadığını kontrol etmek ve sonrasında dosyayı güvenli bir şekilde kapatmak için defer kullanılabilir. Bu sayede, hata olsa bile veya birden fazla çıkış noktası olsa bile dosyanın kapatılması garanti altına alınır. İşte bir örnek:

func writeFile(to path: String, data: Data) {
    let file = openFile(path: path)
    defer {
        closeFile(file)
    }

    if data.isEmpty {
        return // `defer` bloğu yine çalışacak ve dosya kapatılacak
    }

    try writeData(file, data: data)
    // Dosya işlemleri başarıyla tamamlandı, `defer` bloğu sonunda dosyayı kapatır
}

Burada, writeFile fonksiyonu içerisinde dosya açıldıktan sonra, hemen defer bloğu ile dosyanın kapatılması işlemi planlanır. Fonksiyonun herhangi bir noktasından çıkılsa bile (hata oluşsa bile veya erken return yapılsa bile) defer bloğu garantili olarak çalışır ve dosya kapatılır.

Ek olarak, bir fonksiyon içinde birden fazla defer ifadesi kullanılabilir ve bunlar tanımlandıkları sıranın tersi bir sıra ile çalıştırılır. Yani son tanımlanan defer bloğu ilk çalışır. Bu, kaynakların serbest bırakılması gibi işlemlerde oldukça kullanışlı olabilir. Bu özellik, kaynakların kontrolünün daha öngörülebilir ve yönetilebilir olmasını sağlar.

26. Swift’de key path’lerin kullanımını açıklayabilir misiniz? Ayrıca, bu özelliğin iOS uygulamalarında ne gibi avantajlar sağladığını da detaylandırır mısınız?

Swift’deki key path özelliği, bir nesnenin özelliklerine erişim sağlayan, tür güvenli ve esnek bir yol sunar. Özellikle, veri yapıları üzerinde operasyonlar yaparken, örneğin bir dizi nesnenin belirli bir özelliğini çekmek istediğinizde, bu özellik çok işe yarar. Örneğin, kullanıcıların isimlerini içeren bir liste oluşturmak istiyorsanız, map fonksiyonunu kullanarak users.map(\.name) şeklinde bir kullanım sağlayabilirsiniz. Bu yöntem, kodun okunabilirliğini artırır ve yazdığınız fonksiyonların tür güvenliğini garanti altına alır. Key path kullanımı aynı zamanda kod tekrarını azaltır ve dinamik özellik erişimi sayesinde esnek bir yapı sunar. Ancak, kullanıldığı kontekste ve kullanım şekline bağlı olarak performans maliyetleri doğurabileceği unutulmamalıdır. Genel olarak key path, Swift’de veri işleme ve yönetimi için güçlü ve zarif bir araç olarak ön plana çıkar.

27. “Swift dilinde conditional conformances nedir ve ne zaman kullanılır?”

Swift’de conditional conformances, bir tipin belirli koşullar altında bir protokole uyum sağlamasını mümkün kılan bir özelliktir. Bu, genel tiplerin, belirli tür parametrelerine dayalı olarak protokollere uymasını sağlayarak, kodun yeniden kullanılabilirliğini ve esnekliğini artırır.

Örneğin, bir Array türünün sadece içerdiği elemanlar Printable protokolünü uyguladığında Printable olmasını isteyebiliriz. Bu, Array‘in tüm elemanları bir printDescription metodu sağladığında, Array‘in kendisinin de bu metodu sağlamasını ve tüm elemanlarını uygun şekilde yazdırmasını sağlar. Bu durum, where anahtar kelimesiyle extension tanımında belirtilir.

Conditional conformances, Swift’in tip sisteminin gücünü ve esnekliğini artıran önemli bir özelliktir. Bu özellik, özellikle genel programlama yapıldığında, farklı tipler üzerinde çalışırken tip güvenliği sağlamak ve kod tekrarını önlemek için oldukça yararlıdır. Örneğin, farklı tipleri işleyen genel bir fonksiyon veya yapıyı, yalnızca bu tiplerin belirli bir protokole uyduğu durumlarda kullanmak isteyebilirsiniz. Conditional conformances ile bu tiplerin protokol gereksinimlerini dinamik olarak karşılayabilir ve daha temiz, anlaşılır ve yönetilebilir kod yazabilirsiniz.


protocol Printable {
    func printDescription()
}

extension Array: Printable where Element: Printable {
    func printDescription() {
        forEach { $0.printDescription() }
    }
}

struct Person: Printable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func printDescription() {
        print("Name: \(name), Age: \(age)")
    }
}

 let people = [Person(name: "Ali", age: 30), Person(name: "Ayşe", age: 25)]
 people.printDescription()
E-bültene Abone Ol Merak etmeyin. Spam yapmayacağız.

Yazar

İnsanların hayatlarına dokunan uygulamaları geliştirmeyi seven bir iOS yazılım geliştiricisi.

İlgili Yazılar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Hızlı yorum için giriş yapın.

Kayıt Ol

VEYA

Zaten üye misiniz? Giriş Yap

Giriş Yap

VEYA

Henüz üyeliğiniz yok mu? Kayıt Ol