Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

15.Gün - Swift Temellerine Hızlı Bir Bakış

Bu yazıda geçtiğimiz 14 gün boyunca incelediğimiz Swift’in temellerini hızlıca bir gözden geçirip genel tekrar yapacağız. Bu yazıda konun derinlerine inmeden, Swift’in temellerinin genel bir özetini çıkaracağız.

Sabit (Constant) ve Değişkenlerin(Variables) Oluşturulması #

Swift’te sabitler ve değişkenler oluşturulabilir, ancak genellikle sabitler tercih edilir.

Bir String değişkeni oluşturmak ve ardından bunu değiştirmek istiyorum;

var name = "Ted"
name = "Rebecca"

Bir değeri gelecekte değiştirmek istemiyorsak yerine sabit kullanın;

let user = "Daphne"

print() fonksiyonu öğrenme ve hata ayıklama için yararlıdır. Bu user sabitinin değerini gösterir;

print(user)

String #

Swift’te string’ler çift tırnak “ ” arasındadır;

let actor = "Tom Cruise"

Ayrıca emojide kullanabiliriz;

let actor = "Tom Cruise 🏃‍♂️"

String’in içinde çift tırnak kullanmak istiyorsak, önlerine ters eğik çizgi \ konur;

let quote = "He tapped a sign saying \"Believe\" and walked away."

Çok satırlı string kullanmak istiyorsak, aşağıdaki gibi “”” üç çift tırnakla başlayıp bitirmeliyiz;

let movie = """
A day in
the life of an
Apple engineer
"""

Swift stringler için bir çok yararlı method sunmaktadır. .count methodu ile string’in kaç karakterden oluştuğunu öğrenebiliriz;

print(actor.count)

String’in belirli harflerle başlayıp başlamadığını veya bitip bitmediğini bilmemizi sağlayan hasPrefix() ve hasSuffix() methodları da vardır.

print(quote.hasPrefix("He"))
print(quote.hasSuffix("Away."))

Önemli: Swift’te stringler büyük/küçük harfe duyarlıdır. Bu nedenle ikinci kontrol false dönecektir.

Integer #

Swift standart aritmetik operatörleri destekleyen Int türünü kullanarak tam sayıları saklar;

let score = 10
let higherScore = score + 10
let halvedScore = score / 2

Ayrıca değişkenleri yerinde değiştiren bileşik atama operatörlerini de destekler;

var counter = 10
counter += 5

Tamsayıların, isMultiple(of:) yöntemi gibi kendine ait kullanışlı method’ları bulunur.

let number = 120
print(number.isMultiple(of: 3))

Bunun gibi belirli aralıkta rastgele tam sayılar oluşturabiliriz;

let id = Int.random(in: 1...1000)

Decimal #

Ondalıklı bir sayı oluşturursak, Swift bunu bir Double olarak kabul edecektir.

let score = 3.0

Swift Double’ı Int ’ten tamamen farklı bir veri türü olarak kabul eder ve bunları birbirine karıştırmamıza izin vermez.

Boolean #

Swift, true ve false’ı saklamak için Bool türünü kullanır;

let goodDogs = true
let gameOver = false

Bir Boolean’ı toggle() yöntemini çağırarak true’dan false’a çevirebiliriz.

var isSaved = false
isSaved.toggle()

String Interpolation #

String Interpolation kullanarak diğer verilerden string oluşturabiliriz, bunun gibi;

let name = "Taylor"
let age = 26
let message = "I'm \(name) and I'm \(age) years old."
print(message)

//ÇIKTI : I’m Taylor and I’m 26 years old.

Array (Diziler) #

Öğeleri bir Array halinde gruplayabiliriz;

var colors = ["Red", "Green", "Blue"]
let numbers = [4, 8, 15, 16]
var readings = [0.1, 0.5, 0.8]

Bu array’lerin her biri farklı veri türlerinden veriler tutar: colors String, number tamsayılar, readings ondalıklı sayılar.

print(colors[0])
//Red
print(readings[2])
//0.8

İpucu: index numarasına göre veri okurken, o index’te bir öğe bulunduğundan emin olun, aksi takdirde kodumuz çökecek, uygulamamız çalışmayı durduracaktır.

Eğer Array, değişkense (var) yeni öğeler eklemek için append() methodunu kullanabiliriz.

colors.append("Tartan")

Array’de kaç öğe olduğunu öğrenmek için .count , belirli bir index’teki öğeyi silmek için remove(at:) gibi yararlı fonksiyonlar bulunmaktadır.

colors.remove(at: 0)
print(colors.count)
//2

Bir Array’in belirli bir öğeyi içerip içermediğini contains() methodu ile kontrol edebiliriz.

print(colors.contains("Octarine"))
//false

Dictionary (Sözlükler) #

Dictionary belirlediğimiz bir key’e göre birden fazla değeri depolar.

let employee = [
    "name": "Taylor",
    "job": "Singer"
]

Dictionary’den veri okumak için, dictionary’yi oluştururken kullandığımız key’i kullanırız;

print(employee["name", default: "Unknown"])
print(employee["job", default: "Unknown"])

İstediğimiz anahtar mevcut değilse default değer kullanılacaktır.

Set (Kümeler) #

Set’ler yinelenen öğeler ekleyememiz ve öğeleri belirli bir sırada saklamamaları dışında Array’ler benzer.

var numbers = Set([1, 1, 3, 5, 7])
print(numbers)
//[1, 3, 7, 5]

Set’lerin yinelenen değerleri yok sayacağını ve Array’de kullanılan sırayı hatırlamayacağını göz önünde bulundurun.

Bir Set’e öğe ekleme insert() methodu ile yapılır.

numbers.insert(10)

Set’lerin Array’lere göre en büyük avantajı çok hızlı olmalarıdır. 10.000.000 öğeli bir Set contains() methodu ile arama yaptığımızda bile anında yanıt verecektir.

Enum #

Enum, kodumuzu daha verimli ve daha güvenli hale getirmek için oluşturup kullanabileceğimiz adlandırılmış değerler kümesidir. Örneğin haftaiçi günleri için bir enum oluşturabiliriz.

enum Weekday {
    case monday, tuesday, wednesday, thursday, friday
}

Yeni enum Weekday’i çağırır ve haftanın beş gününü işlemek için beş durum sağlar.

var day = Weekday.monday
day = .friday

Type Annotation #

Type annotation kullanarak, değişken veya sabit oluşturulurken türünü kendimiz belirleyebiliriz.

var score: Double = 0

Yukarıdaki kodda Double kısmı olmadan Swift bunun bir Int olduğu sonucuna varırdı, ancak biz bunu geçersiz kılıyoruz ve bunun bir Double olduğunu söylüyoruz.

İşte bazı type annotations örnekleri;

let player: String = "Roy"
var luckyNumber: Int = 13
let pi: Double = 3.141
var isEnabled: Bool = true
var albums: Array<String> = ["Red", "Fearless"]
var user: Dictionary<String, String> = ["id": "@twostraws"]
var books: Set<String> = Set(["The Bluest Eye", "Foundation"])

Array ve Dictionary’lerin kullanımı o kadar yaygındır ki, yazılması daha kolay olan özel söz dizimine sahiptir.

var albums: [String] = ["Red", "Fearless"]
var user: [String: String] = ["id": "@twostraws"]

Boş collections (koleksiyon) oluşturmak için nesnelerin tam türlerini bilmek önemlidir. Örneğin, bunların her ikisi de boş string array’leri oluşturur.

var teams: [String] = [String]()
var clues = [String]()

Bir enum’un değerleri enum’un kendisiyle aynı türe sahiptir, bu nedenle bunu yazabiliriz;

enum UIStyle {
    case light, dark, system
}

var style: UIStyle = .light

Condition (Koşul) #

Bir koşulu kontrol etmek ve uygun şekilde bazı kodları çalıştırmak için if , else ve else if deyimleri kullanılır.

let age = 16

if age < 12 {
    print("You can't vote")
} else if age < 18 {
    print("You can vote soon.")
} else {
    print("You can vote now.")
}

İki koşulu birleştirmek için && kullanabiliriz ve tüm koşul yalnızca içindeki iki parça doğruysa doğru olur.

let temp = 26

if temp > 20 && temp < 30 {
    print("It's a nice day.")
}

Alternatif olarak || veya anlamına gelir. Alt koşullardan en az biri true ise koşulun true olmasını sağlar.

Switch Statement (Switch Case) #

switch / case bir değeri birden fazla koşula karşı kontrol etmemizi sağlar.

enum Weather {
    case sun, rain, wind
}

let forecast = Weather.sun

switch forecast {
case .sun:
    print("A nice day.")
case .rain:
    print("Pack an umbrella.")
default:
    print("Should be okay.")
}

switch ifadeleri kapsamlı olmalıdır. Olası tüm değerler ele alınmalıdır.

Ternary Operator #

Ternary operator, bir koşulu kontrol etmemizi ve iki değerden birini döndürmemizi sağlar: koşul doğruysa bir şey, yanlışsa bir şey.

let age = 18
let canVote = age >= 18 ? "Yes" : "No"

Kod çalıştığında, canVote sabiti “Yes” olarak ayarlanacaktır çünkü age 18 olarak ayarlanmıştır.

Loops (Döngüler) #

Swift’in for döngüleri, bir collection’daki veya özel bir aralıktaki her öğe için bazı kodlar çalıştırır.

let platforms = ["iOS", "macOS", "tvOS", "watchOS"]

for os in platforms {
    print("Swift works on \(os).")
}

Ayrıca bir sayı aralığı üzerinde de döngü yapabiliriz;

for i in 1...12 {
    print("5 x \(i) is \(5 * i)")
}

1…12 1 ve 12 de dahil olmak üzere aralıktaki sayıları içerir. Son sayıyı hariç tutmak istiyorsak ..< kullanmalıyız.

for i in 1..<13 {
    print("5 x \(i) is \(5 * i)")
}

İpucu: Döngü değişkenine (loop variable) ihtiyacımız yoksa _ kullanabiliriz.

var lyric = "Haters gonna"

for _ in 1...5 {
    lyric += " hate"
}

print(lyric)

Bir koşul false olana kadar döngü gövdesini çalıştıran while döngüleri de vardır.

var count = 10

while count > 0 {
    print("\(count)…")
    count -= 1
}

print("Go!")

Geçerli döngü yinelemesini (loop iteration) atlamak ve bir sonrakine geçmek için continue kullanılır.

let files = ["me.jpg", "work.txt", "sophie.jpg"]

for file in files {
    if file.hasSuffix(".jpg") == false {
        continue
    }

    print("Found picture: \(file)")
}

Alternatif olarak, bir döngüden çıkmak ve kalan tüm yinelemeleri atlamak için break kullanılır.

Functions (Fonksiyonlar) #

Yeni bir fonksiyon oluşturmak için, func yazıp ardından fonksiyonumuzun adı yazılır, ardından parantez içindeki parametreleri eklenir.

func printTimesTables(number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(number: 5)

Fonksiyon çağrısı yapılırken number: 5 şeklinde kullanıyoruz. Çünkü parametre adı da fonksiyon çağrısının bir parçasıdır.

Bir fonksiyondan veri döndürmek için, geri dönen verinin hangi tür olduğunu söylemeliyiz. Fonksiyonun içinde değeri döndürmek için de return keyword’ünü kullanmalıyız.

func rollDice() -> Int {
    return Int.random(in: 1...6)
}

let result = rollDice()
print(result)

Fonksiyondan Çoklu Değer Döndürme #

Tuple’lar belirli türde sabit sayıda değer depolar ve bu da bir fonksiyondan birden fazla değer döndürmek için uygun yoldur:

func getUser() -> (firstName: String, lastName: String) {
    (firstName: "Taylor", lastName: "Swift")
}

let user = getUser()
print("Name: \(user.firstName) \(user.lastName)")

Tuple’daki tüm değerlere ihtiyacımız yoksa tuple’ı parçalayıp atama yapılabilir, tuple’daki bir veriyi gözardı etmek için _ kullanılır.

let (firstName, _) = getUser()
print("Name: \(firstName)")

Paremetre Adlarının Özelleştirilmesi #

Bir fonksiyonu çağırırken parametrenin adını belirtmek istemiyorsak, yerine _ konulur.

func isUppercase(_ string: String) -> Bool {
    string == string.uppercased()
}

let string = "HELLO, WORLD"
let result = isUppercase(string)

Diğer bir alternatif ise ilk adın önüne ikinci ad yazmaktır. İlki harici, ikincisi dahili parametre adıdır.

func printTimesTables(for number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(for: 5)

Yukarıdaki kodda for harici, number ise dahili parametre adı olarak adlandırılır.

Parametreler için Varsayılan Değer Sağlama #

Varsayılan parametre değerini, türden sonra eşittir yazıp değer ataması yaparak sağlayabiliriz.

func greet(_ person: String, formal: Bool = false) {
    if formal {
        print("Welcome, \(person)!")
    } else {
        print("Hi, \(person)!")
    }
}

Artık greet() ’i iki farklı şekilde çağırabiliriz.

greet("Tim", formal: true)
greet("Taylor")

Fonksiyonlardaki Hataları Ele Alma #

Fonksiyonlardaki hataları ele alabilmek için, Swift’e hangi hataların olabileceğini söylemeliyiz. Hata fırlatabilen bir fonksiyon yazmamız, ardından bu fonksiyonu çağırmamız ve hataları ele almamız gerekir.

func checkPassword(_ password: String) throws -> String {
    if password.count < 5 {
        throw PasswordError.short
    }

    if password == "12345" {
        throw PasswordError.obvious
    }

    if password.count < 10 {
        return "OK"
    } else {
        return "Good"
    }
}

Şimdi do bloğunu başlatarak hata verebilen fonksiyonu try kullanarak çağıralım ve ardından oluşan hataları yakayalım

let string = "12345"

do {
    let result = try checkPassword(string)
    print("Rating: \(result)")
} catch PasswordError.obvious {
    print("I have the same combination on my luggage!")
} catch {
    print("There was an error.")
}

Hataları yakalamak söz konusu olduğunda, her zaman her türlü hatayı ele alabilecek bir catch bloğuna sahip olmalıyız.

Closures #

Fonksiyonu doğrudan bir sabite veya değişkene atayabiliriz

let sayHello = {
    print("Hi there!")
}

sayHello()

Bu kodda sayHello bir closure’dur. Eğer closure parametre almasını istiyorsak, bu parametreler parantezlerin içine yazılmalıdır.

let sayHello = { (name: String) -> String in
    "Hi \(name)!"
}

in parametre ve dönüş tipinin sonunu işaretlemek için kullanılır. in ’den sonraki her şey closure’ın gövdesidir.

Closure’lar Swift’te yaygın olarak kullanılır. Örneğin, Array’in tüm elemanlarını bir testten geçiren filter() adlı bir method vardır. Bu testi geçenler, yani true olanlar yeni bir Array’de geriye döndürülür.

Bu testi bir closure kullanarak sağlayabiliriz, böylece bir Array’i yalnızca T ile başlayan isimleri içerecek şekilde filtreleyebiliriz.

let team = ["Gloria", "Suzanne", "Tiffany", "Tasha"]

let onlyT = team.filter({ (name: String) -> Bool in
    return name.hasPrefix("T")
})

Closure içinde filter() fonksiyonunun bize aktardığı parametreyi listeliyoruz, bu parametre diziden bir string. Ayrıca closure’ımızın bir Boolean döndürdüğünü söyleriz, ardından closure kodunun başlangıcını in kullanarak işaretleriz, bundan sonra her şey normal fonksiyon kodudur.

Closure Kısa Sözdizimi (Short Syntax) #

Swift, clousre’ların okunmasını kolaylaştıracak bazı çözümler barındırır. T ile başlayan dizi öğlerini filtrelediğimiz kodu buraya tekrar yazalım;

let team = ["Gloria", "Suzanne", "Tiffany", "Tasha"]

let onlyT = team.filter({ (name: String) -> Bool in
    return name.hasPrefix("T")
})

print(onlyT)

Closure’ın gövdesinde yalnızca tek bir kod satırı olduğunu hemen görebiliriz, bu nedenle return ’ü kaldırabiliriz.

let onlyT = team.filter({ (name: String) -> Bool in
    name.hasPrefix("T")
})

filter() fonksiyonuna öyle bir fonksiyon parametre olarak verilmelidir ki, bu parametre olan fonksiyon Array’den bir string kabul etsin ve koşula göre boolean dönsün.

İçeri aktardığımız fonksiyonun bu şekilde davranması gerektiğinden, closure içinde türleri belirtmemize gerek yoktur. Bu sebeple kodu şu şekilde yazabiliriz;

let onlyT = team.filter { name in
    name.hasPrefix("T")
}

Son olarak, Swift bizim için kısa parametre adları sağlayabilir, böylece artık name ve in bile yazmamıza gerek kalmaz, bunun yerine bizim için sağlanan özel olarak adlandırılmış parametreleri kullanırız. $0 gibi.

let onlyT = team.filter {
    $0.hasPrefix("T")
}

Struct #

Struct, kendi property ve methodları olan özel veri türleri oluşturmamızı sağlar.

struct Album {
    let title: String
    let artist: String
    var isReleased = true

    func printSummary() {
        print("\(title) by \(artist)")
    }
}

let red = Album(title: "Red", artist: "Taylor Swift")
print(red.title)
red.printSummary()

Struct’ların örneklerini oluştururken bunu bir initializer kullanarak yaparız. Swift, struct’ı bir fonksiyon gibi ele almamıza ve her bir property için parametre girmemize izin verir. Struct’ın property’lerine göre memberwise initializer otomatik olarak oluşturulur.

Bir struct’ın methodu, property’lerinden birini değiştiriyorsa, bu method mutating olarak işaretlenmelidir.

mutating func removeFromSale() {
    isReleased = false
}

Computed Property #

Hesaplanan property’lerin değeri her erişildiğinde hesaplanır. Örneğin, bir çalışanın kaç gün tatili kaldığını takip eden bir Employee struct yazabiliriz;

struct Employee {
    let name: String
    var vacationAllocated = 14
    var vacationTaken = 0

    var vacationRemaining: Int {
        vacationAllocated - vacationTaken
    }
}

vacationRemaining ’e yazabilmek için hem getter hem de setter sağlamalıyız.

var vacationRemaining: Int {
    get {
        vacationAllocated - vacationTaken
    }

    set {
        vacationAllocated = vacationTaken + newValue
    }
}

newValue Swift tarafından sağlanır ve kullanıcının property’ye atadığı değeri saklar.

Property Observer #

Property observer, property değiştiğinde çalışan kod parçalarıdır. didSet property değiştikten sonra, willSet ise property değişmeden hemen önce çalışır.

Skor değiştiğinde bir mesaj yazdıran bir Game struct oluşturarak, didSet’i gösterebiliriz.

struct Game {
    var score = 0 {
        didSet {
            print("Score is now \(score)")
        }
    }
}

var game = Game()
game.score += 10
game.score -= 3

Custom Initializer #

Initializer, yeni bir struct instance’ını hazırlayan ve tüm property’lerinin bir başlangıç değerine sahip olmasını sağlayan özel fonksiyonlardır.

Swift, Struct’ın property’lerine göre bir tane oluşturur, fakat istersek kendimiz de custom initializer oluşturabiliriz.

struct Player {
    let name: String
    let number: Int

    init(name: String) {
        self.name = name
        number = Int.random(in: 1...99)
    }
}

Önemli : Initializer’ların önünde func yoktur ve explicitly değer döndürmezler.

Access Control #

Swift’in struct’lar içinde erişim kontrolü için çeşitli seçenekleri vardır, en yaygın kullanılan dört tanesi şunlardır;

  • private : “Struct dışında hiçbir şeyin bunu kullanmasına izin verme”
  • private(set) : “Struct’ın dışındaki herhangi bir şey bunu okuyabilir, ancak değiştirmelerine izin verme”
  • fileprivate : “Geçerli dosya dışında hiçbir şeyin bunu kullanmasına izin verme”
  • public : “Herkesin, her yerde bunu kullanmasına izin ver.”

Örneğin;

struct BankAccount {
    private(set) var funds = 0

    mutating func deposit(amount: Int) {
        funds += amount
    }

    mutating func withdraw(amount: Int) -> Bool {
        if funds > amount {
            funds -= amount
            return true
        } else {
            return false
        }
    }
}

funds property’sini private(set) olarak ayarladığımız için, Struct dışından bu property’yi okuyabiliriz fakat yazamayız.

Static Propety ve Method #

Swift static property ve methodlar sayesinde yapının bir örneği yerine doğrudan kendisine eklememize olan verir.

struct AppData {
    static let version = "1.3 beta 2"
    static let settings = "settings.json"
}

Bu yaklaşımı kullanarak, uygulamanın sürüm numarası gibi bir şeyi kontrol etmemiz veya görüntülememiz gereken her yerde AppData.version yazarak okuyabiliriz.

Class (Sınıflar) #

Sınıflar özel veri türü oluşturmamızı sağlar ve Struct’lardan beş açıdan farklıdırlar;

  1. Diğer sınıflardan işlevsellik miras alarak sınıf oluşturabiliriz.

    class Employee {
        let hours: Int
    
        init(hours: Int) {
            self.hours = hours
        }
    
        func printSummary() {
            print("I work \(hours) hours a day.")
        }
    }
    
    class Developer: Employee {
        func work() {
            print("I'm coding for \(hours) hours.")
        }
    }
    
    let novall = Developer(hours: 8)
    novall.work()
    novall.printSummary()
    

    Bir child sınıf, parent sınıftan kalıtım yoluyla aldığı methodu değiştirmek isterse override kullanmalıdır.

    override func printSummary() {
        print("I spend \(hours) hours a day searching Stack Overflow.")
    }
    
  2. Sınıflardaki Initializer biraz daha karmaşık olabilir. Önemli üç noktası bulunmaktadır;

    1. Swift, sınıflar için memberwise initializer oluşturmaz.
    2. Bir child sınıfın custom initializer’ı varsa, kendi property’lerini ayarlamayı bitirdikten sonra her zaman parent sınıfın initializer’ını çağırmalıdır.
    3. Bir child sınıfın herhangi bir initializer’ı yoksa, otomatik olarak parent sınıfının initializer’ını miras alır.

    Örneğin;

    class Vehicle {
        let isElectric: Bool
    
        init(isElectric: Bool) {
            self.isElectric = isElectric
        }
    }
    
    class Car: Vehicle {
        let isConvertible: Bool
    
        init(isElectric: Bool, isConvertible: Bool) {
            self.isConvertible = isConvertible
            super.init(isElectric: isElectric)
        }
    }
    

    super , initializer gibi parent sınıfımıza ait yöntemleri çağırmamıza olanak tanır.

  3. Bir sınıfın instance’ının tüm kopyaları verileri paylaşmaktadır. Yani birinde yaptığımız değişiklikler otomatik olarak diğer kopyaları da değiştirecektir.

    Örneğin;

    class Singer {
        var name = "Adele"
    }
    
    var singer1 = Singer()
    var singer2 = singer1
    singer2.name = "Justin"
    print(singer1.name)
    //Justin  
    print(singer2.name)
    //Justin
    

    Sadece birini değiştirmiş olsak da diğeri de değişmiştir her iki print de ekrana Justin yazacaktır. Buna karşılık, Struct instance’ların kopyaları verilerini paylaşmaz.

  4. Sınıfların, bir nesneye yapılan son referans yok edildiğinde çağrılan bir deinitializer’ı vardır.

    Böylece, oluşturulduğunda ve yok edildiğinde mesaj yazdıran bir sınıf oluşturabiliriz;

    class User {
        let id: Int
    
        init(id: Int) {
            self.id = id
            print("User \(id): I'm alive!")
        }
    
        deinit {
            print("User \(id): I'm dead!")
        }
    }
    
    for i in 1...3 {
        let user = User(id: i)
        print("User \(user.id): I'm in control!")
    }
    
  5. Sınıfın instance’ının kendisi sabit (let) olsa bile değişken (var) property’lerini değiştirebiliriz.

    class User {
        var name = "Paul"
    }
    
    let user = User()
    user.name = "Taylor"
    print(user.name)
    //Taylor
    

    Bunun sonucu olarak, sınıflar verilerini değiştiren methodlarda mutating keyword’üne ihtiyaç duymazlar.

Protocol (Protokoller) #

Protokoller, bir veri türünün desteklemesini beklediğimiz işlevselliği tanımlar ve Swift, kodumuzun bu kurallara uymasını sağlar.

Örneğin, aşağıda bir Vehicle protokol tanımlayalım.

protocol Vehicle {
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

Bu protokolün çalışması için gerekli methodları belirler, ancak herhangi bir kod içermez, yalnızca method adlarınıi parametreleri ve dönüş türlerini belirtiriz.

Bir protokole sahip olduğumuzda, gerekli işlevselliği uygulayarak veri türlerinin buna uygun olmasını sağlayabiliriz. Örneğin, Vehicle ile uyumlu bir Car struct oluşturabiliriz.

struct Car: Vehicle {
    func estimateTime(for distance: Int) -> Int {
        distance / 50
    }

    func travel(distance: Int) {
        print("I'm driving \(distance)km.")
    }
}

Vehicle ’da listelenen tüm methodlar, aynı ad, parametreler ve dönüş türleriyle Car içinde tam olarak bulunmalıdır.

Artık Vehicle ’a uyan her türlü türü kabul eden bir fonksiyon yazabiliriz, çünkü Swift hem estimateTime() hem de travel() ‘ın uygulandığını bilir.

func commute(distance: Int, using vehicle: Vehicle) {
    if vehicle.estimateTime(for: distance) > 100 {
        print("Too slow!")
    } else {
        vehicle.travel(distance: distance)
    }
}

let car = Car()
commute(distance: 100, using: car)

Protocol’ler ayrıca property’leri de kapsayabilir. Vehicle protokolümüze eklemelerde bulunalım;

protocol Vehicle {
    var name: String { get }
    var currentPassengers: Int { get set }
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

Bu durum iki property ekler. get ile işaretlenmiş sabit veya hesaplanmış bir property olabilir. get set ile işaretlenmiş olan değişken veya getter ve setter ile hesaplanmış bir property olabilir.

Vehicle ’a uyan tüm türler bu iki property’yi uygulamalıdırlar.

let name = "Car"
var currentPassengers = 1

İpucu : Virgülle ayırarak istediğimiz kadar protokole uyabiliriz.

Extension #

Extension, herhangi bir türe işlevsellik eklememimizi sağlar. Örneğin, Swift’de stringlerin boşluklarını silmek için bir method vardır, bu method’un ismini daha kısa olacak şekilde extension ile yeniden yazabiliriz.

extension String {
    func trimmed() -> String {
        self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

var quote = "   The truth is rarely pure and never simple   "
let trimmed = quote.trimmed()

Yeni bir değer döndürmek yerine, bir değeri doğrudan değiştirmek istiyorsak, yöntemimizi şu şekilde mutating olarak işretlemeliyiz.

extension String {
    mutating func trim() {
        self = self.trimmed()
    }
}

quote.trim()

Extension, türlere bunun gibi computed property’de ekleyebilir.

extension String {
    var lines: [String] {
        self.components(separatedBy: .newlines)
    }
}

components(separatedBy:) methodu, bir string’i belli kriterlere göre bölebilir.

Artık lines property’sini bütün stringler ile kullanabiliriz.

let lyrics = """
But I keep cruising
Can't stop, won't stop moving
"""

print(lyrics.lines.count)

Protocol Extension #

Protocol extension, computed property ve method implementasyonu eklemek için tüm protokolü genişletir. Bu sayede protocol’e uyan tüm türler computed property ve methodları kullanabilir.

Array, Dictionary ve Set, Collection protocol’e uygundur. Bu sebeple üçüne de aşağıdaki gibi computed property ekleyebiliriz;

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

Şimdi bunu kullanabiliriz;

let guests = ["Mario", "Luigi", "Peach"]

if guests.isNotEmpty {
    print("Guest count: \(guests.count)")
}

Bu yaklaşım sayesinde, bir protokolde gerekli methodları listeleyebileceğimiz ve ardından bunların varsayılan implementasyonlarını bir protocol extension içinde ekleyebileceğimiz anlamına gelir. Bu prtocol’ü uygulayan tüm tipler daha sonra bu varsayılan implementasyonları kullanabilir veya gerektiğinde kendi implementasyonlarını sağlayabilir.

Optionals #

Optional, veri yokluğunu temsil eder. Örneğin 0 değerine sahip bir tamsayı ile hiçbir değere sahip olmayan bir tamsayı arasında ayrım yaparlar.

Optional’lerin nasıl çalıştığını inceleyebiliriz;

let opposites = [
    "Mario": "Wario",
    "Luigi": "Waluigi"
]

let peachOpposite = opposites["Peach"]

Bu var olmayan “Peach” key’ine bağlı değeri okumaya çalışır, bu sebeple normal bir string’i kullanamayız. Swift’in çözümü optional olarak adlandırılır. Optional mevcut olabilecek veya olmayabilecek veriler anlamına gelir.

Optional bir string içinde bizi bekleyen bir string olabilir veya hiçbir şey olmayabilir (nil adı verilen ve “değer yok” anlamına gelen özel bir değer). Int, Double, Bool ’un yanısıra enum, struct ve class instance’ları da dahil olmak üzere her türlü veri türü optional olabilir.

Optional verileri doğrudan kullanamayız çünkü boş olma ihtimalleri vardır. Optional’i kullanmak için unwrap etmeliyiz.

Optional’leri unwrap etmek için birkaç seçeneğimiz vardır, ancak en çok kullanılan yöntem şudur;

if let marioOpposite = opposites["Mario"] {
    print("Mario's opposite is \(marioOpposite)")
}

Yukarıdaki kod, optional değeri dictionary’den okur ve içinde bir string varsa unwrap eder, unwrap edilmiş string marioOpposite sabitine yerleştirilir, artık optional değildir. Optional değeri unwrap edebildiğimiz için koşul başarılı olur ve print() kodu çalıştırılır.

Optional’lerin Unwrap Edilmesi için guard Kullanımı #

Swift’in, optional’leri unwrap etmek için guard let adı verilen ikinci bir yolu vardır. Bu yöntem if let ’e çok benzer ancak işleri tersine çevirir. if let, optional bir değere sahipse parantez içindeki kodu çalıştırır. guard let, optional bir değere sahip değilse kodu çalıştırır.

Şunun gibi gözükür;

func printSquare(of number: Int?) {
    guard let number = number else {
        print("Missing input")
        return
    }

    print("\(number) x \(number) is \(number * number)")
}

Bir fonksiyonun girdilerinin geçerli olup olmadığını kontrol etmek için guard kullanırız, kontrol başarısız olursa diye return kullanmamız zorunludur. Ancak, unwrap ettiğimiz optional içinde bir değer varsa, guard kodu bittikten sonra bunu kullanabiliriz.

İpucu : Herhangi bir koşulla beraber guard kullanılabilir.

Nil Coalescing #

Üçüncü bir unwrap etme yolu da nil coalescing denen yöntemdir. Bu yöntem ile bir optional açılır, eğer optional boşsa varsayılan bir değer sağlanır.

let tvShows = ["Archer", "Babylon 5", "Ted Lasso"]
let favorite = tvShows.randomElement() ?? "None"

nil coalescing operatörü, optional’lerin oluşturulduğu birçok yerde kullanışlıdır. Örneğin, bir string’i int’e dönüştürürken, dönüşüm işleminin başarısız olması ihtimaline karşın optional bir Int? oluşturmak yerine nil coalescing kullanımı oldukça mantıklıdır.

let input = ""
let number = Int(input) ?? 0
print(number)

Optional Chaining #

Optional chaining ile optional içinden optional okunur. Şunun gibi;

let names = ["Arya", "Bran", "Robb", "Sansa"]
let chosen = names.randomElement()?.uppercased()
print("Next in line: \(chosen ?? "No one")")

Optional chaining 2.satırda yer almaktadır. “Eğer opsiyonun içinde bir değer varsa, onu aç ve sonra….” dememizi ve daha fazla kod eklememizi sağlar. Bizim durumumuzda “eğer Array’den rastgele bir eleman aldıysak, onu büyük harfle yaz” diyoruz.

Optional try? #

throwing fonksiyon çağrısı yapılırken, try? seçeneğini kullanarak fonksiyonun sonucunu, başarı durumunda değer içeren bir optional’e, aksi takdirde nil değerine dönüştürebiliriz.

enum UserError: Error {
    case badID, networkFailed
}

func getUser(id: Int) throws -> String {
    throw UserError.networkFailed
}

if let user = try? getUser(id: 23) {
    print("User: \(user)")
}

getUser() fonksiyonu her zaman networkFailed hatası fırlatacaktır, ancak neyin fırlatıldığı bizim için önemli değildir. Bizim için önemli olan fonksiyonun çağrısının bize bir user geri gönderip göndermediğidir.


Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 15 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.