Hai sahabat, pada artikel kali ini saya akan mencoba membahas tentang konsep
multithreading di dalam
mobile programming di dalam
platform iOS.
Multithreading adalah sebuah konsep dimana kita dapat membagi setiap
task yang kita perintahkan ke dalam
device untuk di eksekusi. Sebelum masuk lebih jauh, saya akan memberikan sedikit gambaran tentang apa itu
thread.
Thread adalah urutan instruksi yang dikelola oleh sistem operasi secara bebas. Artinya ketika sistem operasi memiliki alokasi memori yang cukup untuk mengeksekusi sebuah task, maka pada saat itu juga memori tersebut akan digunakan untuk memproses task tersebut. Hal ini apabila tidak dikelola dengan baik, biasanya akan menyebabkan perangkat yang kita gunakan mengalami
hang, atau bahkan
crash (
bad UX)
.
Apabila kita ingin membuat aplikasi iOS yang kompleks, responsif, namun pada saat bersamaan berkesan "ringan",
Grand Central Dispatch (GCD) adalah jawabannya.
Apple menggunakan pendekatan GCD untuk membuat
multithreaded programming lebih mudah. GCD memberikan improvisasi yang sistemik serta komprehensif untuk proses
concurrent code execution yang terdapat di dalam hardware iOS serta macOS.
Masalah besar yang dihadapi oleh pengembang perangkat lunak pada saat ini ialah, bagaimana cara mengeksekusi perintah yang rumit di dalam sebuah aplikasi, namun pada saat yang bersamaan aplikasi tersebut masih responsif untuk menangani perintah lain dari pengguna. Solusi saat ini biasanya berujung di penambahan kecepatan CPU (
hardware solutions), sehingga pengguna tidak menyadari adanya eksekusi yang rumit di
background aplikasi. Sementara masalah tersebut masih saja ada, tanpa memikirikan efisiensi dari sisi perangkat lunak.
Untuk lebih jelasnya, saya akan memberikan salah satu contoh implementasi dari GCD di dalam
source code sebuah aplikasi. Pada pembahasan sebelumnya yaitu
Tutorial ios part 1 kita telah belajar membuat suatu aplikasi sederhana yaitu "Hello World". Saya anggap kalian telah mengerti isi dari source code yang ingin saya berikan dibawah ini:
@IBOutlet var startButton: UIButton!
@IBOutlet var resultsTextView: UITextView!
func fetchSomethingFromServer() -> String {
Thread.sleep(forTimeInterval: 1)
return "Hi there"
}
func processData(_ data: String) -> String {
Thread.sleep(forTimeInterval: 2)
return data.uppercased()
}
func calculateFirstResult(_ data: String) -> String {
Thread.sleep(forTimeInterval: 3)
return "Number of chars: \(data.characters.count)"
}
func calculateSecondResult(_ data: String) -> String {
Thread.sleep(forTimeInterval: 4)
return data.replacingOccurrences(of: "E", with: "e")
}
@IBAction func doWork(_ sender: AnyObject) {
let startTime = NSDate()
self.resultsTextView.text = ""
let fetchedData = self.fetchSomethingFromServer()
let processedData = self.processData(fetchedData)
let firstResult = self.calculateFirstResult(processedData)
let secondResult = self.calculateSecondResult(processedData)
let resultsSummary =
"First: [\(firstResult)]\nSecond: [\(secondResult)]"
self.resultsTextView.text = resultsSummary
let endTime = NSDate()
print("Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds")
}
Seperti yang kalian lihat pada method di atas,
task di dalam controller ini terbagi menjadi beberapa bagian. Untuk membuatnya lebih menarik, saya telah menyisipkan
Thread.sleep di setiap function di dalam controller tersebut.
Thread.sleep adalah fungsi untuk memerintahkan aplikasi kita ke dalam state
pause dan tidak dapat melakukan aktivitas apapun.
Kita memiliki
@IBAction func, dimana didalamnya terdapat fungsi
DoWork(). Fungsi tersebut ialah fungsi yang di trigger dari outlet yang disediakan oleh Swift (UIButton), sehingga ketika outlet UIButton tersebut ditekan, maka fungsi
DoWork() akan dieksekusi.
@IBAction func doWork(_ sender: AnyObject) {
let startTime = NSDate()
self.resultsTextView.text = ""
let fetchedData = self.fetchSomethingFromServer()
let processedData = self.processData(fetchedData)
let firstResult = self.calculateFirstResult(processedData)
let secondResult = self.calculateSecondResult(processedData)
let resultsSummary =
"First: [\(firstResult)]\nSecond: [\(secondResult)]"
self.resultsTextView.text = resultsSummary
let endTime = NSDate()
print("Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds")
}
Fungsi
DoWork() tersebut, akan membuat aplikasi mengalami sensasi "hang" yang diakibatkan dari fungsi
Thread.sleep() yang saya sisipkan diawal tadi. Dimana pada keadaan "hang" tersebut,
outlet UIButton akan mengedip dan memudar satu kali tetapi tidak kembali pada keadaan semula. Hal itu disebabkan oleh
Thread.sleep() yang sedang dieksekusi di
background.
Kita akan menerapkan GCD ke dalam method
DoWork(), dimana pada hasilnya nanti method
DoWork() ini akan dieksekusi sepenuhnya di
background, sehingga pengguna tidak akan mengalami sensasi "hang" ketika menekan
outlet UIButton tersebut
.
@IBAction func doWork(sender: AnyObject) {
let startTime = NSDate()
resultsTextView.text = ""
let queue = DispatchQueue.global(qos: .default)
queue.async {
let fetchedData = self.fetchSomethingFromServer()
let processedData = self.processData(fetchedData)
let firstResult = self.calculateFirstResult(processedData)
let secondResult = self.calculateSecondResult(processedData)
let resultsSummary =
"First: [\(firstResult)]\nSecond: [\(secondResult)]"
self.resultsTextView.text = resultsSummary
let endTime = NSDate()
print("Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds")
}
}
Perubahan line of code yang saya lakukan adalah penambahan fungsi
DispatchQueue.global(),
queue tersebut kemudian dialihkan ke fungsi
queue.async() berikut dengan
closure nya. GCD mengeksekusi
closure tersebut kemudian memasukkannya ke dalam
queue, dimana pengeksekusiannya dilakukan di
background thread, dan
outlet UIButton dapat berlaku sebagaimana
behaviour semestinya tanpa mengurangi (
bad UX).