paint-brush
Cách sử dụng RunLoop trong ứng dụng iOStừ tác giả@alekseimarinin
4,338 lượt đọc
4,338 lượt đọc

Cách sử dụng RunLoop trong ứng dụng iOS

từ tác giả amarinin5m2024/03/04
Read on Terminal Reader

dài quá đọc không nổi

Runloop là một vòng lặp phối hợp việc nhận và xử lý các sự kiện đến trong một luồng cụ thể. Theo mặc định, RunLoop chính luôn chạy trong ứng dụng; nó xử lý các tin nhắn từ hệ thống và truyền chúng đến ứng dụng. Các luồng phụ trợ yêu cầu tự xác định nhu cầu về RunLoop và bạn sẽ phải tự cấu hình và chạy nó.
featured image - Cách sử dụng RunLoop trong ứng dụng iOS
amarinin HackerNoon profile picture

Runloop là gì?

RunLoop là một vòng lặp phối hợp việc nhận và xử lý các sự kiện đến trong một luồng cụ thể.


RunLoop có mặt trong mọi luồng, nhưng theo mặc định, nó ở chế độ chờ và không thực hiện bất kỳ công việc nào.


Nhà phát triển có thể chạy nó nếu cần, nhưng nó sẽ không tự động hoạt động. Để làm điều này, bạn sẽ cần phải viết mã.

Runloop giải quyết được vấn đề gì?

Trước hết, RunLoop được thiết kế để quản lý luồng tác vụ đến và thực hiện chúng vào đúng thời điểm.


Điều này dễ nhận thấy nhất khi làm việc với giao diện người dùng, chẳng hạn như khi sử dụng UIScrollView.


Theo mặc định, RunLoop chính luôn chạy trong ứng dụng; nó xử lý các tin nhắn từ hệ thống và truyền chúng đến ứng dụng. Ví dụ về các thông báo như vậy có thể là sự kiện khi người dùng nhấp vào màn hình.


Các luồng phụ trợ yêu cầu tự xác định nhu cầu về RunLoop. Nếu cần, bạn sẽ phải tự cấu hình và chạy nó. Chạy RunLoop theo mặc định không được khuyến khích, nó chỉ được yêu cầu trong trường hợp chúng ta cần tương tác tích cực với các luồng.


Ngoài ra, tất cả các bộ tính giờ trong ứng dụng đều được thực thi trên runloop, vì vậy nếu cần tương tác với chúng trong ứng dụng của mình, bạn chắc chắn cần phải nghiên cứu các tính năng của runloop.

Làm thế nào nó hoạt động?

RunLoop là một vòng lặp và có một số chế độ hoạt động giúp nhà phát triển hiểu khi nào nên chạy một tác vụ cụ thể.


Vì vậy, RunLoop có thể ở các chế độ sau:

  1. Default - Chế độ mặc định, luồng miễn phí và các hoạt động lớn có thể được thực hiện một cách an toàn trong đó.


  2. Tracking - Chuỗi đang bận thực hiện một số công việc quan trọng. Lúc này, tốt nhất bạn không nên chạy bất kỳ tác vụ nào, hoặc ít nhất là chạy một số tác vụ nhỏ.


  3. Initialization - Chế độ này được thực thi một lần trong quá trình khởi tạo luồng.


  4. EventReceive - Đây là chế độ nội bộ để nhận các sự kiện hệ thống, thường không được sử dụng.

  5. Common - Là chế độ giữ chỗ không có ý nghĩa thực tế.


    Trên RunLoop chính, các chế độ này được chuyển đổi tự động; nhà phát triển có thể sử dụng chúng để thực hiện các tác vụ tốn thời gian để người dùng không nhận thấy giao diện bị treo. Hãy xem một ví dụ.


Quản lý chu trình thực thi trong RunLoop khác không hoàn toàn tự động. Bạn cần viết mã cho một luồng sẽ bắt đầu chu trình thực thi vào một thời điểm thích hợp. Ngoài ra, bạn cần phản hồi phù hợp với các sự kiện và sử dụng các vòng lặp vô tận để đảm bảo chu trình thực thi không dừng lại.


Chúng tôi có UIScrollView và chúng tôi cần thực hiện một tác vụ lớn trên luồng chính để người dùng không nhận thấy bất cứ điều gì.


Chúng ta có thể hoàn thành nhiệm vụ theo cách thông thường:


 DispatchQueue.main.async { sleep(2) self.tableView.refreshControl?.endRefreshing() }


Nhưng kết quả sẽ khá tệ. Người dùng sẽ nhận thấy sự chậm trễ đáng kể trong ứng dụng.

Hiệu ứng tiêu cực này xảy ra do chúng ta chạy một tác vụ trên luồng chính mà không chú ý đến những gì đang xảy ra trên đó vào lúc này.


Vì điều này, chúng tôi bắt đầu thực hiện nhiệm vụ lớn của mình vào thời điểm người dùng tương tác với giao diện. Tất nhiên, điều này dẫn đến việc người dùng thấy giao diện bị treo.


Điều này có thể tránh được bằng cách sử dụng cơ chế RunLoop. Hãy thực hiện logic tương tự bằng cách sử dụng điều này:


 CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) { sleep(2) self.tableView.refreshControl?.endRefreshing() }


Hãy để tôi giải thích những gì xảy ra ở đây. Hàm CFRunLoopPerformBlock thêm mã để thực thi thông qua RunLoop. Ngoài khối mã, hàm này còn có 2 tham số quan trọng.


Người đầu tiên chịu trách nhiệm chọn RunLoop nào sẽ thực thi chức năng. Trong ví dụ này, "chính" được sử dụng.


Người thứ hai chịu trách nhiệm về chế độ hoàn thành nhiệm vụ.


Tổng cộng có ba chế độ có thể:

  • Theo dõi - khi người dùng tương tác với giao diện, chẳng hạn như cuộn qua UIScrollView.


  • Mặc định - người dùng không tương tác với giao diện. Tại thời điểm này, có thể hoàn thành một cách an toàn một nhiệm vụ tiêu tốn nhiều tài nguyên.


  • Chung - kết hợp chế độ mặc định và chế độ theo dõi.

    Kết quả chạy chương trình với đoạn code trên sẽ là:

Khi người dùng bắt đầu tương tác với giao diện người dùng (UI), vòng lặp chạy chính sẽ chuyển sang chế độ "theo dõi" và tạm dừng quá trình xử lý tất cả các sự kiện khác để đảm bảo giao diện mượt mà. Khi người dùng ngừng tương tác với giao diện, vòng lặp chạy sẽ trở về chế độ "mặc định" và tiếp tục thực hiện nhiệm vụ của chúng ta.

Bộ hẹn giờ

Ngoài giao diện người dùng, vòng lặp còn được liên kết chặt chẽ với hoạt động của bộ tính giờ.


Bất kỳ bộ đếm thời gian nào trong ứng dụng đều chạy theo vòng lặp và bạn cần hết sức cẩn thận để không mắc lỗi khi làm việc với chúng, đặc biệt nếu chúng chịu trách nhiệm về chức năng quan trọng như xử lý thanh toán.


 Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething }


Bộ hẹn giờ bắt đầu ở chế độ mặc định theo mặc định, vì vậy nó có thể ngừng hoạt động nếu người dùng đang cuộn qua bảng vào lúc này. Điều này là do vòng lặp hiện đang ở chế độ theo dõi. Đây là lý do tại sao mã trong ví dụ có thể không hoạt động chính xác. Bạn có thể khắc phục sự cố này bằng cách thêm bộ hẹn giờ ở chế độ chung.


 let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething } RunLoop.main.add(timer, forMode: .common)


Ngoài ra, bộ hẹn giờ có thể không kích hoạt vào thời điểm dự kiến hoặc hoàn toàn không kích hoạt. Điều này là do RunLoop chỉ kiểm tra bộ tính giờ ở đầu mỗi chu kỳ. Nếu bộ đếm thời gian kích hoạt sau khi RunLoop đã vượt qua giai đoạn này, chúng tôi sẽ không biết về nó cho đến khi bắt đầu lần lặp tiếp theo. Đồng thời, task chạy trên RunLoop càng lâu thì độ trễ sẽ càng lâu.


Để giải quyết vấn đề này, bạn có thể tạo một luồng mới, khởi động RunLoop trong luồng đó, sau đó thêm bộ hẹn giờ vào luồng mà không cần thêm bất kỳ tác vụ nào khác - bằng cách này, bộ hẹn giờ sẽ hoạt động chính xác.


 let thread = Thread { let timer = Timer(timeInterval: 1.0, repeats: true) { timer in // makeSomething } RunLoop.current.add(timer, forMode: .default) RunLoop.current.run() } thread.start()


Kết quả

Trong bài viết này, chúng ta đã xem RunLoop là gì và nó giải quyết được những vấn đề gì trong ứng dụng iOS. RunLoop là một vòng lặp điều phối việc tiếp nhận và xử lý các sự kiện đến trong một luồng cụ thể và nó có một số chế độ hoạt động.


Nó đặc biệt hữu ích khi làm việc với giao diện người dùng (UI) và bộ hẹn giờ vì nó có khả năng thực thi các tác vụ vào đúng thời điểm, cho phép bạn tránh "treo" giao diện và đảm bảo bộ hẹn giờ hoạt động chính xác.


Mặc dù thực tế là làm việc với Run Loop yêu cầu mã hóa bổ sung nhưng đây là một khoản đầu tư đáng giá để cải thiện hiệu quả và tính ổn định cho ứng dụng của bạn.