Sunday, July 1, 2018

Menghindari bad UX dengan menggunakan Multithreading di iOS, Grand Central Dispatch (GCD)

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).

No comments:

Post a Comment