Swift 4 Closure’lar #18
Closure’lar C++, C# ve Java’daki lambda ifadelerinin Swift’teki karşılığıdır. Closure bir fonksiyonun ya da metodun bir ifade içerisinde bildirilip kullanılması anlamına gelir. Closure bildiriminin genel biçimi şöyledir:
{ ([parametre bildirimi]) [-> <geri dönüş değerinin türü>] in [deyimler] }
Closure’lar fonksiyon türlerindendir. Bir closure bildirimi sanki fonksiyonu bildirip onun adresini kullanmak gibidir. Örneğin:
var f: (Int) -> Int
f = {(a: Int) -> Int in return a * a}
işlemi ileride ele alınacak bazı ayrıntılar dışında aşağıdaki ile eşdeğerdir:
var f: (Int) -> Int
func foo(a: Int) -> Int
{
return a * a
}
f = foo
Bir fonksiyonun parametresi fonksiyon türündense biz onu çağırırken argüman olarak closure verebiliriz. Örneğin:
func foo(val: Int, f: (Int) -> Int)
{
print(f(val))
}
foo(10, f: {(a: Int)->Int in return a * a})
Burada foo fonksiyonunun birinci parametresi geri dönüş değeri Int ve parametresi Int türden olan bir fonksiyon türündendir. (Yani parametre değişkeni fonksiyonun adresini alır). Fonksiyon çağrılırken doğrudan aynı türden clousure verilmiştir.
Örneğin:
let names = [“Ali”, “Veli”, “Selami”, “Ayşe”, “Fatma”]
var result = names.sort({(a: String, b: String) -> Bool in return a < b})
print(result)
in anahtar sözcüğünden sonra closure içerisine istenildiği kadar deyim yerleştirilebilir.
Örneğin:
func foo(val: Int, f: (Int) -> Int)
{
print(f(val))
}
foo(10, f: {
(a: Int)->Int in
print(“Ok”)
if a > 0 {
return a * a * a
}
else {
return a * a
}
}) // 1000
Closure parametreleri yine default olarak let biçimdedir. Ancak biz onu var biçiminde de bildirebiliriz. Bu durumda parametre değişkeni üzerinde değişiklik yapabiliriz. Örneğin:
var f: (Int) -> Int
f = {(var a: Int) -> Int in a = a * 2; return a } // a değiştirilebilir
print(f(10))
Eğer biz burada var anahtar sözcüğünü kaldırsaydık a’ya değer atama kısmında error oluşurdu.
Closure’larda fonksiyon parametre türleri hiç belirtilmeyebilir. Bu durumda closure nasıl bir fonksiyon türüne atanmışsa parametrelerin de o türden olduğu kabul edilir.
Örneğin:
var f: (Int, Int) -> Int
f = {(a, b) -> Int in return a + b } // a ve b Int türden
print(f(10, 20))
Eğer parametre türleri belirtilmeyecsekse parantezler de kullanılmayabilir. Örneğin:
var f: (Int, Int) -> Int
f = {a, b -> Int in return a + b } // a ve b Int türden
print(f(10, 20))
Parametre türü belirtilmiyorsa biz artık isimlerin önüne var ya da let anahtar sözcüklerini koyamayız. Default durum yine let biçimdedir.
Anahtar Notlar: Her ne kadar Swift’in resmi gramerinde geçerli değilse de Swift derleyicileri parantezli biçimde tür belirtmeden var ya da let belirleyicilerini kabul etmektedir. Yani aşağıdaki closure Swift’in gramerine göre hatalı olmasına karşın Swift derleyicisi tarafından geçerli kabul edilmektedir:
var f: (Int, Int) -> Int
f = {(var a, let b) -> Int in a = a + b; return a }
print(f(10, 20))
Benzer biçimde clousre’ların atandığı fonksiyon türlerinin geri dönüş değerlerinin türleri de bilindiği için closure’ların geri dönüş değerlerinin türlerinin de belirtilmesine gerek yoktur.
Örneğin:
var f: (Int, Int) -> Int
f = {a, b in return a + b } // a ve b Int türden geri dönüş değeri de Int türden
print(f(10, 20))
Örneğin:
let names = [“Ali”, “Veli”, “Selami”, “Ayşe”, “Fatma”]
let result = names.sort({a, b in return a < b}) // a ve b String türünden, geri dönüş değeri Bool türden
print(result)
Eğer in anahtar sözcüğünden sonra tek bir ifade varsa bu durumda return anahtar sözcüğünün de kullanılmasına gerek yoktur.
Örneğin:
var f: (Int, Int) -> Int
f = {a, b in a * b } // return anahtar sözcüğüne gerek yok
print(f(10, 20))
Örneğin:
let names = [“Ali”, “Veli”, “Selami”, “Ayşe”, “Fatma”]
let result = names.sort({a, b in a < b}) // return anahtar sözcüğüne gerek yok
print(result)
Tabii closure’ın atandığı fonksiyon değişkeninin geri dönüş değeri Void olabilir. Bu durumda in anahtar sözcüğünün yanındaki ifade geri dönüş değeri anlamına gelmez.
Örneğin:
var f: (Int, Int) -> Void
f = { a, b in print(“\(a), \(b)”)} // burada return yok
f(10, 20)
Nihayet closure’larda parametre değişken isimleri bile belirtilmeyebilir. Bu durumda parametre değişkenleri sırasıyla $0, $1, $2, … isimleriyle kullanılabilir. Parametre değişken isimleri bvelirtilmemişse geri dönüş değerinin türü de in anahtar sözcüğü de artık kullanılamaz. Böylelikle closure’lar çok kısa biçimde ifade edilebilmektedir.
Örneğin:
var f: (Int, Int) -> Int
f = { $0 + $1 } // iki parametresinin toplamına geri dönüyor, return’e gerek yok
print(f(10, 20))
Burada parametre isimleri belirtilmediği için in anahtar sözcüğü de kullanılamaz.
Örneğin:
var f: () -> ()
f = { print(“test”)}
f()
Türün let ya da var bildirimleriyle otomatik belirlendiği durumda closure’da tek bir ifade varsa derleyici bu ifadenin türüne bakarak closure’ın geri dönüş değerinin türünü tespit eder.
Örneğin:
let f = {print(“test”)}
print(f.dynamicType) // () -> () çünkü print’in geri dönüş değeri yok
Fakat örneğin:
func foo() -> Int
{
return 100
}
let f = {foo()}
print(f.dynamicType) // () -> Int çünkü foo’nun geri dönüş değerinin türü Int
Tabii closure’ın parametre türleri ve geri dönüş değerleri tam olarak belirtilmişse hedef türün tespiti tam olarak yapılabilir. Örneğin:
let f = {(a: Int) -> Double in return Double(a) * 3.14}
print(f.dynamicType) // (Int) -> Double
Eğer closure’da birden fazla ifade varsa ve return kullanılmamışsa geri dönüş değeri her zaman Void kabul edilir.
Örneğin:
func foo() -> Int
{
return 100
}
let f = {print(“test”); foo()}
print(f.dynamicType) // () -> () çünkü closure’da tek bir ifade yok
Tabii -parametre isimleri ihmal edilsin ya da edilmesin- clousure’da tek bir ifade yoksa ve fonksiyonun geri dönüş değeri varsa return kullanılması zorunludur.
Örneğin:
var f: (Int, Int) -> Int
f = { if $0 > $1 { return $0 + $1 } else { return $0 * $1}}
print(f(100, 20))
Swift’te eğer bir fonksiyon ya da bir metodun son parametresi bir fonksiyon türündense biz o fonksiyonu ya da metodu son argümanı closure olacak biçimde çağıracaksak bu durumda closure içeriğini parametre parantezinden sonra açılan blok içerisinde yazabiliriz.
Örneğin:
func foo(a: Int, f: (Int) -> Int)
{
print(f(a))
}
Burada foo fonksiyonunun birinci parametresi Int türden, ikinci parametresi ise parametresi Int ve geri dönüş değeri Int türden olan fonksiyon türündendir. Bu fonksiyonu normal olarak şöyle çağırırız:
foo(10, f: {(a: Int) -> Int in return a * a})
Burada foo çağrılırken ikinci parametresi closure olarak girilmiştir. İşte bu çağrıyı pratik olarak şöyle de yapabilirdik:
foo(10) {
(a: Int) -> Int in return a * a
}
Ya da örneğin:
foo(10) {
$0 * $0
}
Bir closure dıştaki bloğun değişkenlerini kullanabilir. Closure’lar bu bakımdan iç içe fonksiyonlara benzetilebilir.
Örneğin:
var x = 10
func foo()
{
let i = 20
let f: () -> Int = {() -> Int in return i * x } // let f = { i * x }
print(f())
}
foo() // 200
İçteki fonksiyonların ya da closure’ların dış bloktaki değişkenleri kullanmasına Swift terminolojisinde (C++’ta da aynı terim kullanılıyor) “capturing” denilmektedir. Capture işlemini derleyici arka planda etkin olarak optimize etmektedir.
Örneğin:
func foo()
{
var i = 20
let f: () -> Void = { i *= 2 }
print(i) // 20
f()
print(i) // 40
}
foo()
foo()
Capture edilen değişkenler faaliyet alanını kaybetse bile derleyici bunun kalıcılığını sağlamaktadır. Örneğin bir fonksiyon iç bir fonksiyonla ya da closure ile geri dönebilir. Bu durumda bu iç fonksiyon ya da closure dış fonksiyonun değişkenlerini kullanıyorsa bunlar derleyici tarafından kalıcı hale getirilmektedir.
Örneğin:
func foo() -> () -> Int
{
var i = 20
let f = { () -> Int in i *= 2; return i }
return f
}
var result: Int
var f = foo()
result = f()
print(result) // 40
result = f()
print(result) // 80
var g = foo()
result = g()
print(result) // 40
result = g()
print(result) // 80
Burada foo fonksiyonunun çalışması bittiğinde capture edilmiş i yok edilmemektedir. Tabii her foo çağırımı yeni bir i’nin yaratılmasına yol açar.
Bir fonksiyon ya da metot bir fonksiyon türünden parametre değişkenine sahipse o parametreyi dışarda bir yere aktarmıyorsa derleyici optimizasyonu için parametre @noescape özelliği ile özniteliklendirilebilir.
Örneğin:
func foo(@noescape f: () -> ())
{
f()
}
foo({print(“Ok”)})
Tabi biz parametreyi dışarıda bir yere aktarıyorsak artık @noescape ile belirleme yapamayız.
Örneğin:
var a: [() -> ()] = []
func foo(@noescape f: () -> ())
{
a.append(f) // error
}
Burada append fonksiyonunun parametresi @noescape ile nitelendirilmediği için çağrı errror ile sonuçlanır. Fakat örneğin:
func bar(@noescape f: () -> ())
{
f()
}
func foo(@noescape f: () -> ())
{
bar(f) // geçerli, bar da @noescape ile nitelendirilmiş
}
foo({print(“Ok”)})
Bir fonksiyon ya da metot () -> T türünden bir fonksiyon parametresine sahipse onu hiç küme parantezleri olmadan çağırabiliriz. Bunun için parametre değişkeninin @autoclosure özniteliği ile niteliklendirilmesi gerekir.
Örneğin:
func foo(@autoclosure f: () -> Int)
{
print(f())
}
foo(10 + 20) // geçerli
Örneğin:
func foo(@autoclosure f: () -> Void)
{
print(f())
}
foo(print(“Ok”)) // geçerli
@autoclosure özniteliklendirmesi yapılmış bir fonksiyon ya da metodu fonksiyon ya da closure ile çağıramayız. Örneğin:
func foo(@autoclosure f: () -> Int)
{
print(f())
}
foo({10 + 20}) // error
Swift’te yeni eklenen bir closure kuralı da değer listeleridir (capture value list). Bir closure bildirimin hemen başında köşeli parantezler içerisinde üst bloklardaki değişkenler virgül atomlarıyla ayrılmış bir liste yazılabilir. Bu durumda closure içerisinde bu üst bloktaki değişkenlerin kendileri değil onların closure bloğuna girişteki değerleri kullanılır. Örneğin:
func foo()
{
var a, b: Int
a = 10
b = 20
let f = {
() -> () in print(a + b);
}
a = 30
b = 40
f() // ekrana 70 yazılır
}
foo()
Burada closure’a a ve b’nin kendisi geçirilmiştir. Yani closure çağrıldığında çağrılma noktasındaki değerleri closure kullanır. Ayrıca dış bloktaki değişkenler let değilse closure onları aynı zamanda değiştirebilmektedir. Halbuki biz dış bloklardaki değişkenlerden köşeli parantezler içerisinde bir liste oluşturursak bu durumda closure bildirimine girişte o değişkenlerin değerleri kopyalanarak closure’a aktarılır. Ayrıca clouse içerisinde artık biz bu değişkenlerin değerlerini değiştiremeyiz.
Örneğin:
func foo()
{
var a, b: Int
a = 10
b = 20
let f = {
[a, b] () -> () in print(a + b);
}
a = 30
b = 40
f() // ekrana 30 yazılır
}
foo()
Türleri İsimlendirmek (type aliases)
Swift’te de diğer dillerde olduğu gibi bir türe her bakımdan onun yerini tutabilecek alternatif isimler verilebilir. Tür isimlendirmenin genel biçimi şöyledir:
typealias <yeni tür ismi> = <tür ismi>
Örneğin:
typealias I = Int // I ile Int aynı anlamda
var a: I
a = 100
print(a)
Örneğin:
typealias Proc = () -> ()
var f: Proc
f = { () -> () in print(“ok”)} // f = { print(“Ok”)}
f()
Örneğin:
typealias Proc = () -> ()
var f: Proc
f = { Proc in print(“ok”)} // f = { print(“Ok”)}
f()
Örneğin:
typealias Procs = [() -> ()]
var procs: Procs = [{() -> () in print(“one”)}, {() -> () in print(“two”)} ]
// var procs: Procs = [{print(“one”)}, {print(“two”)} ]
for var f in procs {
f()
}