Đồng bộ và bất đồng bộ khác nhau như nào? Ví dụ, khi các bạn tính 1+ 1 trên máy tính casio bỏ túi thì nó ra luôn là 2, vậy kết quả đó là tức thời thì nó là đồng bộ. Còn giả sử bạn nhập 1 + 1 trên 1 trang web, rồi bấm nút kết quả, trang đó sẽ gọi lên server xử lý được kết quả và trả về lại cho web là 2 để hiển thị cho bạn, thì như vậy bạn phải đợi trong 1 khoảng thời gian nào đó, giả sử bạn dùng mạng miền núi mất 2s mới trả về, thì đó gọi là bất đồng bộ. Nói nôm na, cái gì mà phải chờ đợi thì bất đồng bộ, còn cái gì tức thời thì đồng bộ.
Còn trong 1 chương trình IOS, chúng ta có thể có rất nhiều thứ bất đồng bộ, ví dụ 1 app như sau:
- Sự kiện khi bấm vào 1 nút(button) trên màn hình, ví dụ nút đăng nhập(chúng ta thực sự không biết lúc nào thì user bấm)
- Hiển thị bàn phím hay ẩn bàn phím trên 1 ô text filed
- Tải 1 file ảnh lớn từ internet(không biết bao lâu thì xong, tùy thuộc vào mạng nhà bạn)
- Lưu dữ liệu xuống đĩa
- Chơi 1 bài hát
- Và nhiều nữa…
Và các sự kiện trên đều có thể xảy ra cùng 1 lúc, ví dụ bạn vừa nghe nhạc vừa tải 1 ảnh về, vừa mở ô text filed để nhập gì đó. Vậy chúng ta sẽ xử lý từng sự kiện riêng biệt đó như thế nào?
Tất cả các việc trên đều không ngăn chặn quá trình thực thi của nhau. IOS cung cấp các APIs để thực thi các việc khác nhau trên các luồng khác nhau(thread) trên các lõi khác nhau của CPU. Tuy nhiên việc viết mã song song khá phức tạp, đặc biệt các việc khác nhau lại cần nguồn dữ liệu giống nhau. Thật khó để xác định đoạn mã nào cập nhật dữ liệu trước hoặc đoạn mã nào có được dữ liệu mới nhất. (Ví dụ bạn không thể xác định được bao lâu thì ảnh mới tải xong, ảnh nào là tải xong đầu tiên…)
Cocoa và các API UIKit bất đồng bộ
Apple cung cấp rất nhiều API trong IOS SDK của họ để giúp bạn viết các đoạn mã bất đồng bộ. Bạn có thể đã sử dụng những thứ này tuy nhiên lại không biết mình đã từng sử dụng, cụ thể những thứ phổ biến mà bạn hay dùng như sau:
- NotificationCenter: Để thực thi những đoạn code tại 1 thời điểm không biết trước nào đó, ví dụ khi người dùng cầm điện thoại để nó nằm ngang hay dọc, bàn phím ẩn hay hiện trên ứng dụng.
- The delegate pattern: để thực thi các đoạn code được ủy quyền từ nơi khác, ví dụ bạn muốn xử lý khi có thông báo mới (push notification) đến ứng dụng.
- Grand Central Dispatch: để giúp bạn thực thi các phần công việc. Bạn có thể viết mã để các công việc thực thi trong 1 hàng đợi nối tiếp hay chạy vô số tác vụ đồng thời trên nhiều hàng đợi khác nhau với sự ưu tiên khác nhau.
- Closures: để tách các đoạn mã mà bạn có thể chuyển giữa các lớp, để mỗi lớp có thể quyết định thực thi nó hay không, bao nhiêu lần và khi nào
Vì đa số các lớp của bạn đều thực hiện những công việc không đồng bộ, các thành phần giao diện người dùng(UI) cũng hoạt động không đồng bộ, cho nên không thể xác định được mã của bạn sẽ thực thi theo thứ tự nào.
Tóm lại, ứng dụng của bạn hoạt động tùy thuộc vào điều kiện dữ liệu bên ngoài, như dữ liệu đầu vào người dùng, điều kiện mạng internet hay các sự kiện hệ điều hành khác. Mỗi khi người dùng mở ứng dụng của bạn, thì nó sẽ hoạt động khác nhau tùy thuộc vào các yếu tố bên ngoài đó. Chúng ta không hề nói rằng việc viết chương trình không đồng bộ là không thể, vì công bằng mà nói các API của apple là mạnh mẽ so với các nền tảng khác cung cấp.
Vấn đề là mã không đồng bộ trở nên phức tạp do sự đa dạng các API của apple cung cấp trong framework(SDK) của họ như sau:
Việc sử dụng delegate bạn cần áp dụng theo mẫu, hoặc có lúc bạn lại sử dụng closure thay thế, hoặc cũng có thể dùng notification center. Không có 1 quy tắc chung nào cho các API trên, do vậy việc đọc hiểu cũng như suy luận logic code trở nên khó khăn(thích cái gì thì dùng cái đó, không có nguyên tắc cho developer).
Để kết thúc phần này, chúng ta sẽ nghiên cứu 2 đoạn mã đồng bộ và bất đồng bộ sau:
Synchronous code(mã đồng bộ)
Để thực hiện thao tác cho 1 phần tử của 1 mảng thì bạn đã dùng rất nhiều lần. Đó là logic ứng dụng rất đơn giản và có 2 điều bất biến: 1 là các thành phần của mảng, và 2 là nó thực thi đồng bộ.
Hãy thử đoạn code sau trên playground(xcode):
var array = [1, 2, 3]
for number in array {
print(number)
array = [4, 5, 6]
}
print(array)
kết quả như sau:
1
2
3
[4, 5, 6]
Rõ ràng mặc dù có thay đổi giá trị của array trong vòng for, tuy nhiên kết quả array vẫn không thay đổi trong quá trình thực thi vòng for. Nó chỉ thực sự thay đổi khi thoát ra khỏi vòng for.
Asynchronous code(mã bất đồng bộ)
Hãy tải code ở link sau:
https://github.com/lexuanquynh/RxLearning.git
hoặc mở xcode và tạo 1 chương trình có dạng:
class ViewController: UIViewController {
var array = [1, 2, 3]
var currentIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func onButtonTouched(_ sender: Any) {
print(array[currentIndex])
if currentIndex != array.count-1 {
currentIndex += 1
}
}
}
Mỗi lần bấm vào nút button trên màn hình, thì index sẽ in ra và tăng lên 1 cho đến khi nó khác 3. Nếu bạn không thể hiểu đoạn code trên thì nên xem lại kiến thức căn bản về lập trình swift nhé.
Vấn đề là, giả sử có 1 đoạn code khác cũng sử dụng mảng array trên, và trong khi bạn còn chưa bấm nút để tăng index, thì array bị thay đổi giá trị hay index bị thay đổi giá trị, dẫn đến kết quả không còn là 1, 2, 3 nữa. Do vậy với đoạn code bất đồng bộ như vậy sẽ rất khó quản lý kết quả như mong muốn. May mắn thay, RxSwift lại giúp ta việc đó.
Vậy chúng ta hãy tiếp tục trong bài 3 để hiểu rõ hơn về nó nhé.
One thought on “RxSwift 2: Khái niệm cơ bản về lập trình bất đồng bộ”