apple swift ile ios programlama – 2

Merhaba, yazımıza kaldığımız yerden devam ediyoruz. Önceki yazımızda, Swift’e genel bir bakış yapıp, değişkenler, diziler, karar yapıları ve döngüler konularını incelemiştik. (http://www.berkanuslu.com/apple-swift-ile-ios-programlama-1) Kaldığımız yerden devam edelim.

Fonksiyonlar (Functions)

Swift içerisinde fonksiyonlar da bir hayli esnek kullanılıyor. Fonksiyon tanımlarken funcanahtar kelimesini kullanıyoruz. Fonksiyonun parametreleri ile dönüş değeri arasına da ->işareti ekliyoruz.

func getNotificationText(name: String, from: String, message: String) -> String {
     return "Merhaba \(name), \(from) diyor ki: \(message)"
}

Fonksiyonumuza getNotificationText ismini verdik ve name, from, message isimlerinde 3 parametre aldığını belirttik, bu parametrelerin tiplerini de parametre isimlerinden sonra : (iki nokta üst üste) ile verdik. -> ifadesinden sonra da String değerini vererek fonksiyonumuzun geriye String bir ifade döndüreceğini söyledik. Fonksiyonun içerisinde işlemlerimizi yaptık vereturn ile değerini geri döndürdük. Bu fonksiyonu ise aşağıdaki gibi çağırabiliriz.

var ourNotificationText : String = getNotificationText("Berkan USLU", "Jose Mujica", "Naber aga?")
println(ourNotificationText)

Ayrıca Swift’te fonksiyon değerleri içerisinde birden fazla geri dönüş değeri tanımlayabilirsiniz.

func getTop5PlayerList() -> (String, String, String, String, String) {
     return ("Hagi", "Hakan Şükür", "Sergen Yalçın", "Alex", "Hami Mandıralı")
}

Yukarıdaki gibi -> ifadesinden sonra 5 adet String değeri döneceği belirtilmiş ve returnifadesinden sonra bu değerler sırayla gönderilmiş. Dönüş değerlerine ulaşmak için ise fonksiyonu çağırdıktan sonra . (nokta) koyarak istediğimiz değere erişebiliriz.

var bestPlayers = getTop5PlayerList()
println(bestPlayers.1) //Hakan Şükür

Parametre olarak verdiğimiz değerleri de istersek sınırsız parametre gönderecek şekilde tanımlayabiliriz. Bunun için değişkenin tipi belirlenirken sonuna … (üç nokta) koyarak istediğimiz kadar parametreyi fonksiyona gönderebiliriz ve bu gönderilen parametreler bir dizi içerisinde tutulur.

func ortalamaHesapla(numbers: Int...) -> Int {
     var ort = 0
     for number in numbers {
         ort += number
     }
     return ort / numbers.count
}

Yukarıdaki gibi verdiğimiz sayıların ortalamalarını alan bir fonksiyon yazdık. Bu sayede istediğimiz kadar sayıyı fonksiyona gönderdiğimiz zaman tüm gönderilen parametreleri numbers isimli bir array içerisinde saklıyor. Daha sonra tümünün toplamını bir for-in döngüsü içerisinde hesaplattıktan sonra array eleman sayısını bulmak için count özelliğini kullanıyoruz. Fonksiyonu ise aşağıdaki gibi kullanabiliriz.

var ortalama = ortalamaHesapla(50, 20, 30, 40, 70)
println(ortalama) //42

Swift’in fonksiyon tarafındaki esnekliği tıpkı Javascript’i andırıyor, bunun da en güzel örneklerinden birisi fonksiyonların iç içe tanımlanabiliyor olmasıdır. Ayrıca bu esnek yapıda fonksiyonlar başka fonksiyonlara parametre olarak bile gönderilebilmektedir.

func​ ​returnTotal​() -> ​Int​ {
​    ​var​ ​y​ = ​10
​    ​func​ ​add​() {
​        ​y​ += ​5
​    }
​    ​add​()
​    ​add​()
​    ​return​ ​y
​}
​returnTotal()

Yukarıdaki kodda returnTotal() isimli fonksiyon çağırıldığınca öncelikle y değişkenine 10 değeri atanıyor. Daha sonra add() isimli fonksiyon tanımlanmış ve burada y değişkenine 5 eklenmiş, returnTotal içerisinde ne kadar add() fonksiyonu çağırılırsa y değişkenine 5 ekleyecek yani sonuç olarak burada 20 değeri geri dönecektir.

func​ ​hasAnyMatches​(​list​: [​Int​], ​condition​: ​Int​ -> ​Bool​) -> ​Bool​ {
​    ​for​ ​item​ ​in​ ​list​ {
​        ​if​ ​condition​(​item​) {
​            ​return​ ​true
​        }
​    }
​    ​return​ ​false
​}
​func​ ​lessThanTen​(​number​: ​Int​) -> ​Bool​ {
​    ​return​ ​number​ < ​10
​}
​var​ ​numbers​ = [​20​, ​19​, ​7​, ​12​]
​hasAnyMatches​(​numbers​, ​lessThanTen​)

Yukarıdaki örnekte ise fonksiyonun bir başka fonksiyona parametre olarak gönderilmesi örneği var. hasAnyMatches() fonksiyonuna bakarsak list isiminde bir Int dizisini parametre alıyor. İkinci parametresinin ismi ise condition ve bunun tipini de fonksiyon olduğunu gösteren Int -> Bool ifadesi yer alıyor. Yani bu “condition isminde ikinci bir parametrem var ve sen bu parametreye bir fonksiyon göndereceksin bu fonksiyon da Inttipinde değişkeni alıp bana Bool tipinde değer döndürmesi gerekiyor” anlamına geliyor.

Şimdi kullanıma gelirsek hasAnyMatches(numbers, lessThanTen) ifadesini görüyoruz. Burada numbers Int tipinde bir dizi, lessThanTen de Int tipinde bir parametre alarakBool tipinde geriye değer döndüren yani hasAnyMatches() fonksiyonumuzun tanımındaki özellikleri taşıyan bir fonksiyondur. Bundan sonrası aslında klasik fonksiyon gibi davranıyor. Ancak basit bir farklılık var.

hasAnyMatches() fonksiyonunun içeriğine baktığımızda list ismindeki dizi parametresini bir for-in döngüsünde her bir elemanını item değişkeninde tutarak for-in döngüsünün içerisine giriyor, daha sonra burada if bloğunda condition ismindeki parametresi yani bizimlessThanTen() metodumuza işaret eden fonksiyon dizinin item ismindeki değişkenlerinicondition() metoduna (yani aslında lessThanTen() metoduna) gönderiyor. Bu metot da içeride sayının 10′dan küçük olup olmadığına bakıp geriye Bool bir değer döndürüyor. Eğer true değer dönerse hasAnyMatches() içerisindeki if bloğu içerisine giriyor vehasAnyMatches() metodu da true değer dönüyor. Dizinin hiç bir elemanı 10′dan küçük değilse geriye false değeri dönecektir.

Nesne ve Sınıflar (Object and Classes)

Swift içerisinde nesne ve sınıf yapıları da basit bir fonksiyon tanımlar gibi sade tutulmuş.class anahtar kelimesini kullanarak bir sınıf oluşturmak mümkün ve property (özellik) tanımlaması yaparken yine sabit (constant) ve değişken (variable) tanımlaması yapılabiliyor. Aynı zamanda sınıf içerisinde fonksiyon tanımlamasında da değişklik yok. Aşağıda basit bir sınıf örneği görmektesiniz.

class​ ​Vehicle​ {
​    ​var​ ​doorCount: Int
​    ​var​ ​brandName​: String

​    ​init​(doorCount: Int, brandName: String) {
​        ​self.doorCount = doorCount
​        ​self.brandName = brandName
​    }

​    ​func​ ​getVehicleInformation​() -> ​String​ {
​        ​return​ ​"\(brandName) markalı araç \(doorCount) kapılıdır."
​    }
​}

Yukarıda basit bir sınıf yapısı görmekteyiz. Bu yapıyı biraz inceleyecek olursak Vehicle isminde bir sınıf tanımlanmış, bu sınıfa doorCount ve brandName isminde iki property (özelllik) tanımlanmış daha sonra sınıfımız için init ve getVehicleInformation isminde iki fonksiyon tanımı yapılarak basit bir sınıf oluşturulmuştur.

DİKKAT: Sınıfların tüm property elemanlarına değer ataması (assign) yapılması zorunludur. Direk tanımlama yapıldığında atama işlemi yapılacağı gibi herhangi bir atama yapılmadan init() metodu içerisinde de değer ataması (assign) yapılabilir.

Burada init() metodu sınıfın oluşturulurken tüm property elemanlarına değer ataması (assing) yapılmasını sağlar. init() metodu sınıf oluşturulduğunda çağırılır ve başına func tanımı almaz. Diğer programlama dillerinde sınıfın ismi ile tanımlanan ve constructor (yapıcı metot) olarak geçen bu kullanım Swift’te init() metodu ile kullanılmaktadır. init() içerisinde kullanılan self ise sınıfın kendi nesnesine işaret eder ve burada self. şeklinde ulaştığımız yukarıda tanımlanan sınıfın property elemanlarıdır. Vehicle sınıfının kullanımı ise aşağıdaki gibidir.

let ourVehicle = Vehicle(doorCount:2, brandName: "Türk Traktör")

Yukarıdaki gibi Vehicle sınıfından ourVehicle isimli bir nesne oluşturulmuştur. ourVehiclenesnesinin markası Türk Traktör’dür ve 2 kapısı vardır. Bu tanımlama ile birlikte init()metodu otomatik olarak çağırılarak doorCount ve brandName property elemanlarına gerekli parametrelerle gönderilen değerler atanacaktır.

println(ourVehicle.getVehicleInformation())

Aynı zamanda nesnenin fonksiyonlarına erişmek için de nesne isminden sonra . (nokta) ile birlikte fonksiyonumuzun adını yazıyoruz. Şimdi bir Vehicle sınıfı tanımlayarak sınıflara giriş yaptık bir de nesne yönelimli programlama (object-oriented programming) konusunun olmazsa olmazlarından olan kalıtım hakkında bir örnek yapalım ve bir sınıftan alt sınıflar türetelim.

class​ ​Car: Vehicle​ {
​    ​var​ ​color: String
​    var passengerCount: Int

​    ​var estimatedWeight: ​Double​ {
​        get {
​        ​        return passengerCount * 100.0;
        }
​        set {
​        ​        passengerCount = newValue / 100.0;
        }
​    }

​    ​init​(doorCount: Int, brandName: String, color: String, passengerCount: Int) {
​        ​super.init(doorCount: doorCount, brandName: brandName)
​        ​self.color = color
​        ​self.passengerCount = passengerCount
​    }

​    ​override func​ ​getVehicleInformation​() -> ​String​ {
​        ​return​ ​"\(brandName) markalı \(color) renkli araba \(doorCount) kapılıdır ve \(passengerCount) ile tahmini ağırlığı \(estimatedWeight) kg ölçülmüştür."
​    }
​}

Yukarıdaki Car sınıfı Vehicle sınıfından türetilmiş yeni bir sınıftır. Vehicle‘ın private olmayan tüm elemanlarına erişebilir. Yani ayrıca bir daha Car sınıfında, Vehicle içerisinde tanımlıdoorCount ve brandName property elemanlarını tanımlamamıza gerek yoktur.

init() metodu içerisinde de Vehicle sınıfının init() metodunu kullanabiliriz. Türettiğimiz sınıfın türetildiği sınıfa erişmek için super nesnesi kullanılır. self nesnesi sınıfın kendi nesnesine işaret ederken super nesnesi de sınıfın türetildiği üst sınıfın nesnesine işaret eder. Yanisuper.init() diye çağırdımız metot aslında Vehicle sınıfının init() metodudur.

Bir diğer farklı durum da yeni türettiğimiz sınıfta (yani Car), türetilen üst sınıfla (yani Vehicle) aynı isimde bir fonksiyon kullanmak istersek o zaman da func tanımından önceoverride metodu kullanmamız gerekmektedir. Böylelikle Vehicle sınıfındaki fonksiyon yok sayılarak Car sınıfındaki fonksiyon çağırılır. Ancak biz yine de istersek super.getVehicleInformation() diyerek Vehicle sınıfındaki metodu da çağırabilir.

let ourCar: Car = Car(doorCount:5, brandName: "Honda", color: "Siyah", passengerCount: 3)

Car sınıfından oluşturduğumuz nesneyi de yukarıdaki gibi kullanırız.

println(ourCar.getVehicleInformation())

Car nesnesinin getVehicleInformation() fonksiyonunu çağırdığımızda geriye renk bilgisini, taşınan kişi sayısını ve tahmini ağırlığı da döndürdüğünü görürüz. Burada bir de farklı olarak get/set tanımlamalarını içeren bir estimatedWeight property elemanı bulunmaktadır. Bu sayede bazı değişkenlere erişirken farklı hesaplamalar ve farklı durumları da tetikletmek içinget/set tanımlamaları ile property oluşturulur.

println(ourCar.estimatedWeight)
ourCar.estimatedWeight = 600.0
println(ourCar.passengerCount)

Yukarıdaki gibi bir kullanımda öncelikle 3 kişinin ağırlığı tahmini olarak 300 kg olarak hesaplanacak ve ikinci satırda estimatedWeight property değerini 600′e eşitlediğimizde set içerisine girerek yeni girilen değeri -ki bu değer newValue ismiyle tutulur- 100′e bölerek yolcu sayısını belirlediğimiz için 3. satırda arabanın yolcu sayısının 6 olduğunu görürüz.

let ourCar: Car? = Car(doorCount:5, brandName: "Honda", color: "Siyah", passengerCount: 3)
let passengerCount = ourCar?.passengerCount

Son olarak sınıflar ve nesneler konusunu optional ifadesi ile tanımladığımızda neler oluyor ona bakalım. optional önceki yazıda bahsettiğimiz gibi ? ile tanımlanıyordu. Sınıf tanılanırken tipinden sonra ? kullanarak veya nesne kullanılırken nesne isminden sonra ?kullanarak soru işaretinden sonra nil olan tüm işlemleri yoksaymış oluruz.

Sayımlama ve Yapılar (Enumeration and Structures)

Sayımlama (evet Türkçe’si çok saçma gelebilir ama TDK böyle söylüyor) enum anahtar kelimesi ile tıpkı sınıflar gibi tanımlanan bir yapıdır. Ayrıca enum yapıları içerisinde metot da barındırabilirler. Aşağıda basit bir enum yapısı örneği var.

enum​ ​Rank​: ​Int​ {
​    ​case​ ​Ace​ = ​1
​    ​case​ ​Two​, ​Three​, ​Four​, ​Five​, ​Six​, ​Seven​, ​Eight​, ​Nine​, ​Ten
​    ​case​ ​Jack​, ​Queen​, ​King
​    ​func​ ​getDescription​() -> ​String​ {
​        ​switch​ ​self​ {
​        ​case​ .​Ace​:
​            ​return​ ​"ace"
​        ​case​ .​Jack​:
​            ​return​ ​"jack"
​        ​case​ .​Queen​:
​            ​return​ ​"queen"
​        ​case​ .​King​:
​            ​return​ ​"king"
​        ​default​:
​            ​return​ ​String​(​self​.​rawValue​)
​        }
​    }
​}

Yukarıda basit bir kart oyunundaki sayıları gösteren bir enum örneği verilmiştir. Bu örneğe baktığımızda tanımladığımız enum için Rank ismini veriyoruz. Yani burada aslında bir sıralama yapıyoruz ve bunu da Int tipinde tutuyoruz. İlk değer olarak Ace belirledik ve değerini 1 olarak verdik. Bundan sonra tanımladığımız tüm elemanlar sırasıyla 1′den sonra 1 artırılarak numaralandırılacaktır. Eğer istersek bir fonksiyon tanımlayarak tüm elemanları String’e çevirebiliriz. Buradaki bir fark da dikkatinizi çektiyse herhangi bir şekilde fonksiyona bir parametre verilmemiş, bunun yerine self anahtar kelimesi kullanılarak mevcut enumifadesi kullanılmış ve switch içerisinde karşılaştırma yapılmıştır. enum elemanlarının Inttipinden değerlerine ulaşmak için rawValue isminde bir anahtar kelime kullanılmaktadır. Aşağıdaki örnek bunu gayet iyi açıklamaktadır.

let king = Rank.King
println(king) //ekrana (Enum value) yazacaktır
let kingRaw = king.rawValue
println(king.getDescription()) // ekrana "king" yazacaktır
println(king.rawValue) //ekrana 13 yazacaktır.

Yukarıda enum tipinin kullanımı yapılmıştır. enum tiplerini herhangi bir sınıf içerisinde veya ileride göreceğimiz struct içerisinde kullanabiliriz. king nesnesinin rawValue property elemanına eriştiğimizde Rank.King‘in sıra numarasına ulaşırız. Aynı şekilde tanımladığımızgetDescription() metodunu kullanarak Rank.King‘in String olarak karşılığına erişiriz.

Yapılar (struct), tıpkı sınıflar gibi tanımlanarak, sınıflarla aynı özellikleri içerirler. Ancak bazı yönleri ile sınıflardan ayrılırlar. Bunlardan en belirgin olarak bilinen fark, yapılar değer tipinde tutulurken sınıflar referans tipinde tutulurlar. Yani bu da sınıflarla yapıların saklanma yerlerini ve erişim hızlarını etkilemektedir. Ancak yapılar veri tipi belli olan değer tipinde tutuldukları için çok büyük miktarda veriyi barındıramazlar. Bir diğer önemli fark ise yapılar (structs) zaten inherited oldukları için bir kez daha türetilemezler. Bu yüzden de abstract, protected, virtual, internal gibi sınıf özelliklerini içeremezler ve farklı bellek bölgesinde tutuldukları için yıkıcı (destructor) metodu içermezler. Kısacası sınıflardan daha küçük, belli veri tiplerini organize etmek için kullanılabilirler diyebiliriz.

​struct​ ​Card​ {
​    ​var​ ​rank​: ​Rank
​    ​var​ ​suit​: ​Suit
​    ​func​ ​getDescription​() -> ​String​ {
​        ​return​ ​"The ​\(​rank​.​getDescription​())​ of ​\(​suit​.getDescription​())​"
​    }
​}

Yukarıdaki gibi struct anahtar kelimesi ile tanımlanmış bir Card isimli yapımız mevcut ve bu yapı içerisinde Rank ve Suit isminde iki enum tipli değişkenimiz bulunmaktadır. Basit olarak bir de getDescription() metodu tanımlanmıştır. Bu metot seçili olan Rank veSuit‘in String‘e döndürülmesini sağlamaktadır. Struct yapılarında otomatik olarak yapıcı (constructor) metot oluşturulur. Kullanımı ise aşağıdaki gibidir.

let fourOfClubs = Card(rank: Rank.Four, suit: Suit.Clubs)
println(fourOfClubs.getDescription())

Protokoller ve Uzantılar (Protocols and Extensions)

Protokoller class, enum ve struct yapılarının hepsine uygulanabilir ve bir kalıp oluşturmaya yarar. Protokoller içerisinde herhangi bir değer ataması vb. yapılmaz sadece tanımlama işlemleri yapılır. Java’daki abstract veya interface yapısına benzer bir kullanımı vardır. Aşağıda basit bir tanımını görerek başlayalım.

protocol FirstProtocol {
    var name: String { get }
    mutating func getInfo()
}

Yukarıda FirstProtocol isminde bir protokol tanımlanmış ve bu protokol içerisinde nameisimli bir değişken ve getInfo() isimli bir metot tanımlanmış. Şimdi bu gibi protokol ve birden çok protokolü nasıl kullanırız ona bakalım.

struct SomeStructure: FirstProtocol, AnotherProtocol {
    var name: String = "SomeStructure bla bla"
    mutating func getInfo() {
        name += " bla2"
    }
}

Buradaki gibi bir yapı (strcut) ya da sınıf (class) bir veya birden fazla protokolden oluşabilir. Buradaki kullanıma baktığımızda Java’daki implements ile eklenen Interface tanımları gibi olduğunu görürüz.

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    var name: String = "SomeStructure bla bla"
    func getInfo() {
        name += " bla2"
    }
}

Yukarıda da sınıf tanımlamasına bir örnek verilmiştir. Yukarıdaki tanımlara baktığımızda protokol içerisinde mutating anahtar kelimesi ile getInfo() metodunun tanımlandığını görüyoruz. Bu işlem aynı şekilde struct içerisinde de mutating anahtar kelimesi ile birlikte kullanılırken class içerisinde normal şekilde kullanılmış, bunun sebebi ise struct ve enumyapıları kendi içlerinde kendi değişkenlerini değiştiremezler, çünkü değer tipinde veri tutarlar. Kendi örnekleri (instance) üzerindeki değişkenlere işlem yaptırabilmeleri için metotların başına mutating anahtar kelimesini eklemek gerekmektedir. Class yapılarında buna gerek olmamasının sebebi sınıfların kendi örneklerine (instance) müdahale edebiliyor olmalarıdır.

Extension yapıları ise önceden tanımlanmış tiplere fonksiyon veya bazı hesaplanmış alanlar eklemek için kullanılır ve extension anahtar kelimesi ile tanımlanır.

​extension​ ​Int​: ​FirstProtocol​ {
​    ​var​ ​name​: ​String​ {
​        ​return​ ​"The number is ​\(​self​)​"
​    }
​    ​mutating​ ​func​ ​getInfo​() {
​        ​self​ += 32
​    }
​}

Yukarıdaki tanımlamada Int tipine FirstProtocol isimli protokol ile az önce tanımladığımız özelliklerin kattık ve bu özelliklerin tanımlamalarını yaptık. Artık herhangi bir Int tipinde ifadenin name isminde bir property elemanı ve getInfo() isminde bir de metodu oldu.

12.name
12.getInfo()

Generic Yapısı

Bir çok programlama dilinde geçen ve <T> şeklinde tanımlanan generic yapısı aynı zamanda Swift içerisinde de benzer şekilde tanımlanmaktadır. Bu şekilde tanımlanan bir Generic yapısı ile birlikte tipten bağımsız olarak işlem yapmak mümkün hale gelmektedir.

func simpleMax(x: T, y: T) -> T {
    if x < y {
        return y
    }
    return x
}

Yukarıdaki tanımlamada simpleMax isimli metot parametre olarak x ve y isminde T tipinde iki değişken alıp içeride büyük-küçük kontrolü yapıp yine T tipinde bir değişken geri döndürmektedir.

simpleMax(17, 42) // T = Int
simpleMax(3.14159, 2.71828) // T = Double

Yukarıdaki kullanıma göz attığımızda Generic tanımlamaların önemi anlaşılmış olur. Burada değişkene parametre olarak Int tipinde bir değişken gönderildiğinde Int geri dönecek,Double tipinde bir değer gönderildiğinde de Double geri dönecektir.

Son konumuzdan da bahsettikten sonra epey uzayan bu yazıyı bitirelim. Artık yeni yazılarımızda Swift ile Mobil Uygulama Geliştirme konularıyla (daha kısa yazılarla) devam edeceğiz.

Herkese iyi çalışmalar…