Swift: Multithreading
Uygulamanızdaki kodları DispatchQueue API’ını kullanarak farklı thread’lerde çalıştırın!
Merhaba sevgili TurkishKit okuyucuları.👋 Bu yazımızda uygulamanızda bitirilmesi uzun süren bir kod çalıştırdığınızda arayüzün donmamasını sağlarken kullanacağınız “multithreading” özelliklerinden bahsedeceğim.
Öncelikle yapacağımız şey hakkında biraz temel bilgi elde edelim.🧑💻
İşlemciler, aynı anda sadece bir işlemi gerçekleştirmek için tasarlanmıştır. Zamanla aynı anda çalışan işlemlerin sayısı arttıkça bu işlemlerin tümünü daha hızlı bir şekilde yapabilmek için çoklu çekirdekli işlemciler ortaya çıkmıştır.📈
Aslında şu an satılan bilgisayar işlemcilerindeki her bir çekirdek, ayrı bir işlemciyi temsil etmektedir.
Bilgisayardaki işlemlere ise thread adı verilmektedir. Bir uygulama birden fazla thread’e sahip olabilir. Böylece aynı anda birden fazla işlem yapabilir.
Ayrıca bir işlemci/çekirdek de aynı anda birden fazla thread çalıştırabilir. Bunu, çalışması gereken thread’leri sıraya koyarak yapar.
Normalde yazdığınız bir uygulamada kodlar, arayüz ile aynı thread’de çalışarak uzun bir işlem gerçekleştiğinde arayüzün donmasına sebep olabilir. ⚠️ Bu da kullanıcı deneyimi için tahmin edebileceğiniz gibi hiç güzel bir şey olmayacaktır. 😬
Swift’te birden fazla thread oluşturmak için Apple’ın sunduğu DispatchQueue
API’ını kullanabiliriz.
DispatchQueue’nun nasıl kullanıldığını göstermek için sırayla Apple’dan bazı isimlere “Merhaba, isim” şeklinde cümleler yazan bir uygulama geliştireceğiz.
Öncelikle sadece bir isime hoşgeldin mesajı yazarak uygulamanın temelini atalım.
import SwiftUIstruct ContentView: View {
var body: some View {
Text(“Merhaba, Tim”)
.font(.largeTitle)
}
}struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Uygulamamız sorunsuz bir şekilde çalışıyor. Şimdi isimlerin listesini tutması için bir dizi ve şu anki ismi tutması için bir değişken oluşturalım. Bu isimleri sırayla değiştirmesi için ise onAppear
metodunu ve for
döngüsünü kullanalım.
import SwiftUIstruct ContentView: View { let isimler = ["Tim", "Craig", "Jeff", "Lisa", "Alan"]
@State var gosterilenIsim = "" var body: some View {
Text("Merhaba, \(gosterilenIsim)")
.font(.largeTitle)
.onAppear(perform: {
while true {
for isim in isimler {
gosterilenIsim = isim
usleep(750000)
}
}
})
}
}struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Uygulamamız şu an çalışıyor ancak ekranda hiç bir şey gözükmüyor. Bunun sebebi ise onAppear
metodunun içine yazdığımız kod çalıştırılırken arayüzün güncellenememesi.
Başka bir deyişle isimleri sırayla değiştirdiğimiz kod çalışıyor ancak bir thread aynı anda sadece bir işlemi yapabildiği için arayüz yeni isimlerle güncellenemiyor.
Şimdi onAppear
metodunun içindeki kodu aşağıdaki ile değiştirelim:
DispatchQueue.global(qos: .userInteractive).async {
while true {
for isim in isimler {
gosterilenIsim = isim
usleep(750000)
}
}
}
Uygulamamız tekrardan istediğimiz gibi çalışıyor. Verdiğimiz belirli bir süre aralığı ile “Merhaba, isim” cümlesinin isim kısmı değişiyor.
Peki bunu sağlayan ne?
DispatchQueue
aracılığıyla oluşturduğumuz yeni thread, içindeki while döngüsünün asenkron olarak başka bir yerde çalışmasını sağlayarak arayüzün thread’ini durdurmuyor.
DispatchQueue.global(qos: .userInteractive).async {
// Yeni thread'de çalışan kod buraya gelmeli.
}
DispatchQueue.global()
metodunu kullanırken verdiğimiz qos
argümanı parametresi “quality of service”, yani servis kalitesi anlamına geliyor. Sistem, yeni thread’e bu argümanda belirttiğimiz şekilde öncelik tanıyor.
“qos” argümanının alabileceği değerler ise aşağıdaki gibidir:
- userInteractive: Çalışan işlemin kullanıcı ile interaktif bir şekilde çalıştığını belirtir. Animasyonlar gibi kullanıcı arayüzünü güncelleyen işlemlerde kullanılabilir.
- userInitiated: Kullanıcı arayüzüyle direkt olarak bağlantılı olmasa da çalıştığı süre boyunca kullanıcıların uygulamayı kullanmasını engelleyen işlemlerde kullanılabilir.
- default: İşlemin önceliğine sistem tarafından karar verilecektir.
- utility: Kullanıcının aktif olarak takip etmediği işlemlerde kullanılabilir.
- background: Temizlik veya bakım için arkada çalışan, uzun sürebilecek işlemler için kullanılabilir.
Bu değerler, sistemin önemli thread’lere öncelik vermesi için kullanılsa da tek kullanım alanı bu değil. Bildiğiniz gibi Apple, bir süredir yongalarına farklı güçte işlemci çekirdekleri koyuyor.
Örneğin A14 çipinde 2 tane yüksek performans, 4 tane de yüksek verimlilik çekirdeği bulunuyor. İşlemler, ne kadar güç gerektirdiğine göre otomatik olarak gerekli çekirdeklerde çalıştırılıyor.
DispatchQueue
kullanırken dikkat etmeniz gereken başka bir şey de yeni işlemin asenkron veya senkron olduğu.
onAppear
metodundaki kodu aşağıdaki kod ile değiştirelim:
DispatchQueue.global(qos: .userInteractive).sync {
while true {
for isim in isimler {
gosterilenIsim = isim
usleep(750000)
}
}
}
Uygulamamız tekrardan çalışmıyor. Bunun sebebi, yeni bir işlem oluşturmamıza rağmen bu işlemi senkron olarak oluşturmamız. Senkron olarak oluşturduğumuz işlem, bitmeden önce kendinden sonraki işlemin gelmesini engelliyor. Böylece ilk durumdaki gibi arayüz, yazdığımız kod çalışırken güncellenemiyor.
Başka bir deyişle, onAppear
fonksiyonunun içindeki kod farklı bir işlemde çalışsa bile bitene kadar diğer kodların çalışmasını engelliyor. Diğer kodların içinde SwiftUI ile yazdığımız arayüz de bulunduğu için arayüzdeki güncellemeleri göremiyoruz.
Gördüğünüz gibi Apple, uygulamamızda birden fazla thread kullanabilmemiz için kullanımı oldukça kolay bir API sunuyor. 🎉 DispatchQueue
adındaki bu API sayesinde thread’lerin önemleri ve senkron/asenkron oldukları belirtilebiliyor. Geriye kalan bütün işlemleri ise sistem gerçekleştiriyor.
Ben örnekleri SwiftUI ile yazılmış bir arayüz kullanarak göstersem de bu API’ı kullanabilmek için tabii ki SwiftUI kullanmamız gerekmiyor. DispatchQueue
, Swift ile yazılan her türlü uygulamada kullanılabiliyor.
Umarım sorununuzu çözmenize yardımcı olabilmişimdir. Bir sonraki yazımızda görüşmek üzere.👋