15.Gün - Swift Temellerine Hızlı Bir Bakış
Table of Contents
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;
-
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.") }
-
Sınıflardaki Initializer biraz daha karmaşık olabilir. Önemli üç noktası bulunmaktadır;
- Swift, sınıflar için memberwise initializer oluşturmaz.
- 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.
- 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. -
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.
-
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!") }
-
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.