iOS Concurrency - Phần 3.2: Grand Central Dispatch

Xử lý background tasks

Chắc hẳn các bạn đã gặp phải trường hợp tableView hay collectionView với các cell của nó phải load image từ internet về. Khi chúng ta scroll thì nó không còn mượt (smooth) và cảm giác như không có responsive. Đây là một vấn đề hay gặp phải với tableView hay collectionView. Dưới đây là đoạn code mà chúng ta hay dùng để load image cho tableViewCell (hay collectionViewCell) từ Internet.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell
    
    let urlString = “https://..”
    let data =  NSData(contentsOf: URL(string: urlString)!) //1
    cell?.img.image = UIImage(data: data as! Data) //2
    return cell!
  }

Như mình đã trình bày ở bài trước, mỗi một ứng dụng chúng ta có một main thread để xử lý các tác vụ liên quan tới UI. Ở đây chúng ta đã thực hiện một task đòi hỏi thời gian lâu là download hình ảnh từ internet về (//1) trên main thread. Thêm vào đó, main thread là một serial queue do đó chúng ta sẽ phải đợi việc download hình ảnh về rồi mới tiến hành các công việc khác (//2), điều này làm ứng dụng chúng ta không mượt và bị lag do thời gian đợi quá lâu. Chúng ta sẽ khắc phục chúng bằng cách thay đoạn code sau:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell
    
    let urlString = “https://..”
    DispatchQueue.global(qos: .userInitiated).async { //3
      let data =  NSData(contentsOf: URL(string: urlString)!)
      DispatchQueue.main.async { //4
        cell?.img.image = UIImage(data: data as! Data)
      }
    }
    return cell!
  }

Ở bước số 3 chúng ta chuyển task đòi hỏi thời gian lâu này xuống background global queue và chạy chúng trong closure một cách bất đồng bộ (async). Điều này làm cho hàm của chúng ta kết thúc nhanh trên main thread và tạo người dùng cảm giác mượt mà. Ở bước số 4 chúng ta tiến hành gán hình ảnh đã down vào trong UIImageView. Bởi vì việc cập nhật UI phải được thực hiện trên main queue nên chúng ta tạo một main queue và chạy đoạn code cập nhật hình ảnh. Bằng việc thay thế đoạn code trên, tableView (collectionView) sẽ scroll mượt mà hơn khi chúng ta load những hình ảnh từ internet về cell của chúng.

Hoãn việc chạy một task nào đó (Delaying task execution)

DispatchQueue cho phép chúng ta delay một task nào đó sau một khoảng thời gian nhất định. Chúng ta có thể sử dụng chúng để chỉ rõ khi nào một task chạy. Thử tưởng tượng bạn sử dụng một app ở lần đầu tiên, bạn có thể bối rối vì không biết sử dụng sao. Thật là tốt nêu chúng ta hiển thị cho người dùng một gợi ý (prompt) nào đó. Tuy nhiên nếu chúng ta hiển thị gợi ý khi view chính vừa được load lên, người dùng sẽ không để ý nó bởi vì có thể họ sẽ nhìn một phần khác của màn hình. Nhưng nếu chúng ta hiển thị gợi ý sau 1s kể từ khi view được load lên, nó sẽ trở nên nổi bật và người dùng sẽ thấy nó. Tạo một project và bỏ đoạn code dưới đây vào viewDidLoad bạn sẽ cảm nhận được.

let delayInSeconds = 1.0 //1
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { //2
      self.navigationItem.prompt = "I love you"
    }

Ở bước 1 chúng ta đặc tả thời gian delay theo đơn vị giây. Bước 2, chúng ta sẽ đợi sau khoảng thời gian đã đặc tả ở trên để chạy bất đồng bộ đoạn code update UI trên main thread. Chúng ta nên sử dụng asyncAfter với Main Queue (serial) và thận trọng khi sử dụng với Concurrent Queue.

Những gì mình sẽ nói tiếp?

Thông qua bài giới thiệu nho nhỏ này, các bạn chắc hẳn đã biết cách xử lý background task khi chúng ta gặp phải một tác vụ nặng, đòi hỏi thời gian lâu. Thêm vào đó chúng ta còn biết tăng cường trải nghiệm người dùng (UX) bằng cách sử dụng dispatch queue để delay việc hiển thị gợi ý (prompt). Ở bài tiếp theo mình sẽ giới thiệu một số vấn đề liên quan đến Singleton và cách giải quyết chúng với GCD. Hẹn gặp các bạn ở những bài viết kế tiếp trong chuỗi series về iOS concurrency.

Tài liệu tham khảo

https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1