Kiến thức phỏng vấn iOS _ Phần 2: Grand Central Dispatch (GCD) và ứng dụng (tt)
Như đã nói ở phần trước, hôm nay mình sẽ đi vào các class phổ biến của Dispatch như DispatchQueue, DispatchGroup, DispatchSemaphore, DispatchSource,... và ứng dụng thực tế. Như chúng ta đã biết, ở Swift 3, thì GCD đã được làm mới, chuyển từ C-based API sang một API mới gần với ngôn ngữ tự nhiên ...
Như đã nói ở phần trước, hôm nay mình sẽ đi vào các class phổ biến của Dispatch như DispatchQueue, DispatchGroup, DispatchSemaphore, DispatchSource,... và ứng dụng thực tế.
Như chúng ta đã biết, ở Swift 3, thì GCD đã được làm mới, chuyển từ C-based API sang một API mới gần với ngôn ngữ tự nhiên hơn.
Ví dụ: DispatchQueue(dispatch_queue_t): class quản lý những tác vụ mà chúng ta execute và submit.
DispatchGroup(dispatch_group_t): là một group những block được submitted đến những hàng đợi (queues) làm việc bất đồng bộ.
DispatchSemaphore(cũ là dispatch_semaphore_t): Sử dụng một semaphore khi muốn đợi một block hoàn thành rồi mới tiếp tục thực thi code phía dưới của chỗ gọi.
DispatchSource: gửi các notifications khi các events xảy ra trong việc truy cập tài nguyên trong hệ thống
Dispatch groups là một cách để block 1 thread đến khi một hoặc nhiều tác vụ hoàn thành việc thực thi của mình. Chúng ta sẽ sử dụng nó ở những nơi mà ta không chắc chắn được việc thực thi của tất cả những tác vụ là lúc nào sẽ hoành thành. Ví dụ như, khi download một list các image trên server thì ta nhóm nó vào 1 group để biết được khi nào thì các tác vụ download sẽ xong.
//tạo 2 hàng đợi let queue1 = DispatchQueue.global() let queue2 = DispatchQueue.global() //tạo 1 group let group = DispatchGroup() //add task vào queue1 queue1.async(group: group, qos: .background, flags: .enforceQoS) { for _ in 0..<3 { print("queue1") } } //add task vào queue2 queue2.async(group: group, qos: .background, flags: .enforceQoS) { for _ in 0..<4 { print("queue2") } } //Làm 1 vài việc print("1") print("1") print("1") //đợi những tác vụ trong group xong xuôi group.wait(timeout: DispatchTime.distantFuture) //group xong thì làm tiếp việc khác print("2") =============== Kết quả demo queue1 1 queue2 1 1 queue1 queue1 queue2 queue2 queue2 2
Khi vô phỏng vấn, nếu người ta có hỏi: Khi mở app lên thì chúng ta sẽ đồng bộ những loại dữ liệu mới cập nhật từ server, vậy khi nào thì ta detect được là nó xong?. Thì mình chỉ cần trả lời là mình sẽ group các queue đó lại trong một group, group sẽ quản lý được tiến độ làm việc của các queue bên trong nó.
Một dispatch semaphore giống như là một semaphore truyền thống, tuy nhiên nó hiệu quả hơn. Dispatch semaphore gọi xuống kernel chỉ khi thread gọi cần phải bị chặn bỏi vì semaphore không khả dụng. Nếu semaphore khả dụng thì sẽ không gọi xuống nhân. Đó là định nghĩa về semaphore, nó quá chung chung và rất khó để hiểu một cách thấu đáo. Một cách dễ hiểu hơn thì chúng ta có thể hiểu như thế này, nếu chúng ta sử dụng một tài nguyên hữu hạn nào đó thì ta cần phải quy định một hạn mức sử dụng nhất định, nếu dùng quá mức thì không cho dùng nữa, hiểu đơn giản như vậy. Một trường hợp áp dụng cụ thể của semaphore trong iOS đó là việc thực thi tác vụ trong các hàng đợi. Xem một đoạn code mẫu
public func lema(completionHandler: (() -> Void)?) { let queue = DispatchQueue.global(qos: .background) queue.async { for i in 0..<3 { print(i) } completionHandler?() } } print("start") lema { } print("end") ============= start 0 end 1 2
Trình tự chạy sẽ là in chuỗi "start", sau đó gọi hàm lema, trong hàm lema này có 1 cái khối lệnh closure, trong closure lại có 1 queue chạy bất đồng bộ. Ta thấy kết quả trả về rất bình thường, start -> 0 -> end -> 1 -> 2. Vì lý do bất đồng bộ ở một thread khác main nên việc in kết quả lộn xộn thế này là xảy ra một cách hoàn toàn bình thường. Vâỵ, làm thế nào để chữ end được in ra sau cùng, hay nói cách khác là hàm lema chạy xong xuôi rồi mới thực thi lệnh dưới nó. Đó là 1 vấn đề để áp dụng semaphore vào. Xem code có semaphore
public func lema(completionHandler: (() -> Void)?) { let queue = DispatchQueue.global(qos: .background) queue.async { for i in 0..<3 { print(i) } completionHandler?() } } print("start") //tạo một semaphore có value là 0 let sema = DispatchSemaphore(value: 0) lema { print("send sema") sema.signal() } sema.wait(timeout: .distantFuture) print("end") ========== Kết quả: start 0 1 2 send sema end
Trên đây là một cách mà để chúng ta áp dụng semaphore vào trong code của mình, nó giống như trong thực tế, khi bạn lưu một thông tin user login vào local bằng bất đồng bộ, thì bạn muốn nó được lưu xong rồi mới làm tiếp thì nên áp dụng semaphore này vào, ...
Khi bạn làm việc với system, bạn phải chuẩn bị task vụ đó sẽ thực hiện trong một khoảng thời gian nào đó. Vì việc gọi đến kernel hoặc các lớp hệ thống khác liên quan đến việc thay đổi context , thì sẽ được so sánh xem thử là việc gọi đó nó tốn kém nào trong quá trình thực thi, nói đơn giản đó là mỗi lần gọi hệ thống thì đều có độ trễ nhất định. Vì thế mà, có nhiều thư viện cung cấp những giao diện bất đồng bộ cho phép code submit 1 request đến hệ thống và làm các việc khác trong khi request đang được xử lý. CGD cũng dựa trên điều này mà nó cho phép mình submit request của chúng ta và báo lại kết quả bằng cách sử dụng blocks và dispatch queues.
Dispatch source là một kiểu dữ liệu cơ bản cho việc xử lý những sự kiện ở level thấp.
Thật sự thì Dispatch Source mình cũng chưa làm việc với nó nên cũng không tiện nói nhiều ở đây.
Trong khuôn khổ của một ngữ cảnh đi phỏng vấn thì mình nghĩ nói được những ý trên cũng đủ để người phỏng vấn biết được mình có nắm được những kiến thức cơ bản về dispatch, còn nếu vô sâu hơn chắc mọi người nên tự tìm hiểu, vì một loại dispatch ở trên đều vô cùng rộng và sâu, một loại co viết cả quyển sách cũng chưa chắc hết nên mình cũng viết những gì mình biết. Hi vọng nó sẽ giúp ích được ai đó cần. Xin cảm ơn.