Có một thành phần rất quan trọng đóng vai trò trái tim của App trong iOS, đó chính là runloop.
Cũng như tên gọi của nó, runloop là một vòng lặp ở thread sử dụng để chạy các trình xử lý sự kiện để đáp ứng các event được gửi đến. Nó giống như một hộp thư mà sẽ đợi thông điệp với và gửi tới đúng chỗ nhận thông điệp đó.
Một runloop cơ bản có thể như sau:
while(1) { dispatch_event() }
Runloop trong iOS chính là CFRunLoop được implement bằng ngôn ngữ C. Function CFRunLoopRunSpecific()
được gọi thông qua public API CFRunLoopRun()
và CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled)
sẽ được gọi lặp đi lặp lại để xử lý event cho đến khi nào runloop kết thúc.
Runloop lắng nghe event từ nhiều loại source khác nhau. Input source cung cấp event không đồng bộ( asynchronous), thường là thông điệp từ luồng khác hoặc ứng dụng khác. Timer source gửi event đồng bộ( synchronous) xảy ra tại một thời điểm theo lịch hoặc thời gian lặp lại.
Một thuật ngữ quan trọng trong CFRunLoop chính là CFRunLoopModes. CFRunLoop làm việc với Sources, Sources được đăng ký trên một hoặc nhiều mode. Và runloop cũng được chạy trên một mode nhất định. Khi có một event được gửi tới từ một source, nó sẽ được xử lý nếu mode của source giống mới mode của runloop. Thường runloop sẽ run trên mode default( kCFRunLoopDefaultMode).
Giả sử một timer được add vào mode Tracking( Mode này dùng để xử lý sự kiện khi scroll), mà runloop đang chạy ở mode Default thì event của timer sẽ không được xử lý bởi runloop.
kCFRunLoopCommonModes
Trong runloop có lưu trữ một mảng commonModes. Khi một source được thêm vào runloop với kCFRunLoopCommonModes thì runloop sẽ tự động apply chúng vào tất cả những mode mà define bên trong mảng commonModes. Mục đích sinh ra kCFRunLoopCommonModes để đăng ký nhiều mode cho một source.
Giả sử trong commonModes đang có 2 mode là Tracking và Default. Một timer nào đó được add vào runloop với mode là kCFRunLoopCommonModes, nếu runloop run ở một trong 2 mode Tracking và Default thì lúc đó event của timer sẽ được xử lý.
Input Sources
Input sources gửi các sự kiện không đồng bộ tới thread. Source của event phụ thuộc vào loại input source, thường là một trong 2 loại port-based(source1) và custom source(non port-based, Hay còn gọi là source0). Sự khác biệt giữa chúng là cách chúng được báo hiệu. Port-base được báo hiệu tự động thông bởi Kernel, còn custom source được báo hiệu bằng tay từ một thread khác.
Khi bạn tạo một input source, bạn phải xác định một hoặc nhiều mode( sử dụng kCFRunLoopCommonModes).
Port-Based Sources( Source1)
Trong Cocoa, bạn chỉ việc tạo ra port object và sử dụng phương thức scheduleInRunLoop:forMode:
trong NSPort để add port đó vào runloop.
Trong Core Foundation(CF), bạn phải tự tạo cả port và runloop source.
Custom Input Sources( Source0)
Để tạo custom input source, bạn phải sử dụng phương thức CFRunLoopSourceCreate
trong Core Foundation.
Cocoa Perform Selector Sources
Khi chúng ta sử dụng hàm performSelector, Cocoa sẽ tạo ra một custom input source và thêm vào runloop, Selector sẽ được chạy ở thread được chỉ định. Cocoa Perform Selector Sources sẽ được xoá khỏi runloop sau khi nó được gọi trên selector đã xác định.
Lưu ý: Khi gọi performSelector sang thread khác, thì bắt buộc thread đó phải đang có một runloop đang hoạt động.
Timer Sources
Timer source cung cấp các event cho thread của bạn tại một thời điểm định sẵn trong tương lai một cách đồng bộ( synchronous). Timer là một cách để thread thông báo một điều gì đó.
Mặc dù nó tạo ra các thông báo dựa trên thời gian, tuy nhiên timer không phải một cơ chế real-time. Giống như input sources, timer được liên kết với một hoặc nhiều mode của runloop. Khi thời gian định trước của timer xảy ra sau khi hàm check timer được chạy thì timer phải đợi tới vòng lặp tiếp theo mới được callback.
Run Loop Observers
Runloop hỗ trợ chúng ta việc theo dõi một số hoạt động chính của runloop để phục vụ cho mục đích nào đó. Bạn có thể theo dõi những hoạt động sau đây:
- Bắt đầu vào vòng lặp runloop
- Chuẩn bị xử lý timer
- Chuẩn bị xử lý sources
- Chuẩn bị sleep, đợi source hoặc timer được kích hoạt.
- Sau khi runloop được đánh thức.
- Kết thúc vòng lặp runloop
Runloop xử lý như thế nào
Mỗi một vòng lặp runloop thực hiện xử lý event như sau:
- Thông báo cho observers biết đã bắt đầu chạy runloop.
- Thông báo cho observers biết timer đã sẵn sàng được kích hoạt.
- Thông báo chuẩn bị xử lý sources.
- Xử lý block và source0.
- Thông báo chuẩn bị sleep nếu có.
- Thread sẽ sleep cho tới khi một trong những event dưới đây xảy ra:
– Một event tới từ port-based input source(source1).
– Một timer được kích hoạt.
– Runloop timeout.
– Runloop được đánh thức chủ động( Gọi hàmCFRunLoopWakeUp
). - Thông báo runloop sau khi được đánh thức.
- Xử lý event đang đợi xử lý
– Xử lý timer.
– Xử lý GCD async main.
– Xử lý source1.
– Xử lý block.
* Kiểu tra điều kiện kết thúc vòng lặp: Finished, Stopped, TimedOut, HandledSource nếu một trong các điều kiện xảy ra thì tới bước 9. Ngược lại thì quay lại bước 2. - Thông báo cho observers rằng runloop đã kết thúc.
Có một số bước đã bị loại bỏ so với document của apple bởi vì source code đã thay đổi.
Điều kiện kết thúc vòng lặp:
- kCFRunLoopRunTimedOut: Hết thời gian lặp
- kCFRunLoopRunFinished: runloop rỗng, ví dụ tất cả source, timer đều đã bị xoá hết.
- kCFRunLoopRunHandledSource: Source1 đã được xử lý, event đã được gửi đi.
- kCFRunLoopRunStopped: runloop được chủ động dừng khi gọi
CFRunLoopStop()
Các hàm xử lý event trong runloop
Chắc hắn các bạn khi debug chương trình mà breakpoint được dừng chắc cũng thấy ở callstack function kiểu như __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
Mỗi vòng lặp của của runloop sẽ thực hiện dispatch event bằng những function sau:
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) static void _CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(void (*perform)(void *), void *info)
Mỗi function sử lý một kiểu source khác nhau. Ta sẽ xem xét từng cái.
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
Khi bạn gọi dispatch_async sang Main Queue thì runloop sẽ xử lý event này. và function này để xử lý gọi block đó.
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
Bằng cách sử dụng CFRunLoopObserver
bạn có thể theo dõi hoạt động của runloop ví dụ như bắt đầu runloop, kết thúc runloop,… Có 7 option để bạn có thể theo dõi:kCFRunLoopEntry
, kCFRunLoopBeforeTimers
, kCFRunLoopBeforeSources
, kCFRunLoopBeforeWaiting
, kCFRunLoopAfterWaiting
, kCFRunLoopExit
, kCFRunLoopAllActivities
. Function này chính là dùng để gọi callback cho observer runloop.
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
Function này để gọi hàm callback của timer hoặc khi bạn sử dụng performSelector:afterDelay:
.
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
Khi bạn sử dụng function CFRunLoopPerformBlock()
để thực hiện chạy block trong vòng lặp tiếp theo của runloop. Thì hàm CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
sẽ được sử dụng để invoke block này.
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
Hàm xử lý event source0. Source0 phải được gửi signal bằng tay và gọi CFRunLoopWakeUp
từ trong ứng dụng.
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
Hàm xử lý event source1. Source1 được gửi signal tự động tới từ Kernel.
Best practice
Khi sử dụng realm observe, thì chúng ta chỉ được sử dụng trên thread nào có runloop. Giả sử chúng ta dùng main thread thì ta không cần làm gì cả vì main thread đã chạy sẵn runloop. Tuy nhiên khi chúng ta muốn chạy trên thread khác, thì chúng ta phải chạy một runloop, sau đó khi cần gọi code observe ta cần dùng hàm CFRunLoopPerformBlock
thay vì dispatch_async
để chuyển luồng.
Tổng kết
Như vậy là qua bài viết chúng ta đã nắm được khái niệm runloop, các loại mode, nguyên lý hoạt động của runloop.
Cảm ơn các bạn đã đọc bài viết.
Tham khảo thêm:
https://bou.io/RunRunLoopRun.html