UIScrollView tạm dừng NSTimer cho đến khi quá trình cuộn kết thúc


84

Trong khi một UIScrollView(hoặc một lớp dẫn xuất của nó) đang cuộn, có vẻ như tất cả những NSTimersthứ đang chạy sẽ bị tạm dừng cho đến khi cuộn xong.

Có cách nào để giải quyết vấn đề này không? Đề bài? Một cài đặt ưu tiên? Có gì không?



Câu trả lời:


201

Một giải pháp dễ dàng và đơn giản để thực hiện là:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2
UITrackingRunLoopMode là chế độ - và công khai của nó - bạn có thể sử dụng để tạo các hành vi hẹn giờ khác nhau.
Tom Andersen

Yêu thích nó, hoạt động như một sự quyến rũ với GLKViewController. Chỉ cần đặt controller.paused thành CÓ khi UIScrollView xuất hiện và bắt đầu bộ đếm thời gian của riêng bạn như được mô tả. Đảo ngược nó khi chế độ xem cuộn bị loại bỏ và thế là xong! Vòng lặp cập nhật / kết xuất của tôi không đắt lắm, vì vậy điều đó có thể hữu ích.
Jeroen Bouma

3
Tuyệt vời! Tôi có phải gỡ bỏ bộ đếm thời gian khi tôi làm mất hiệu lực của nó hay không? Cảm ơn
Jacopo Penzo

Tính năng này hoạt động để cuộn nhưng dừng hẹn giờ khi ứng dụng chuyển sang chế độ nền. Bất kỳ giải pháp để đạt được cả hai?
Pulkit Sharma

23

Đối với bất kỳ ai sử dụng Swift 3

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

7
Tốt hơn là nên gọi timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)như lệnh đầu tiên thay vì Timer.scheduleTimer(), vì scheduleTimer()thêm bộ đếm thời gian vào runloop và lệnh gọi tiếp theo là một cách thêm vào cùng một runloop nhưng với chế độ khác. Đừng làm cùng một công việc hai lần.
Accid Bright

8

Đúng, Paul nói đúng, đây là vấn đề về vòng lặp chạy. Cụ thể, bạn cần sử dụng phương thức NSRunLoop:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

7

Đây là phiên bản nhanh chóng.

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

6

Bạn phải chạy một luồng khác và một vòng lặp khác nếu bạn muốn bộ hẹn giờ kích hoạt trong khi cuộn; vì bộ tính giờ được xử lý như một phần của vòng lặp sự kiện, nếu bạn đang bận xử lý việc cuộn chế độ xem của mình, bạn sẽ không bao giờ xem được bộ hẹn giờ. Mặc dù lỗi hiệu suất / pin khi chạy bộ hẹn giờ trên các luồng khác có thể không đáng để xử lý trong trường hợp này.


11
Nó không yêu cầu một chuỗi khác, hãy xem câu trả lời gần đây hơn từ Kashif. Tôi đã thử nó và nó hoạt động mà không cần thêm chủ đề.
progrmr

3
Bắn đại bác vào chim sẻ. Thay vì một luồng, chỉ cần lên lịch hẹn giờ ở chế độ vòng chạy phù hợp.
uliwitness

2
Tôi nghĩ câu trả lời của Kashif dưới đây là câu trả lời tốt nhất vì nó chỉ yêu cầu bạn thêm 1 dòng mã để khắc phục sự cố này.
tiệt.

3

cho bất kỳ ai sử dụng Swift 4:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)

1

tl; dr the runloop đang thực hiện cuộn nên nó không thể xử lý bất kỳ sự kiện nào nữa - trừ khi bạn đặt bộ đếm thời gian theo cách thủ công để nó cũng có thể xảy ra khi runloop đang xử lý các sự kiện chạm. Hoặc thử một giải pháp thay thế và sử dụng GCD


A phải đọc cho bất kỳ nhà phát triển iOS nào. Rất nhiều thứ cuối cùng được thực thi thông qua RunLoop.

Bắt nguồn từ tài liệu của Apple .

Run Loop là gì?

Một vòng lặp chạy rất giống với tên của nó. Nó là một vòng lặp mà chuỗi của bạn nhập vào và sử dụng để chạy các trình xử lý sự kiện để đáp ứng với các sự kiện đến

Làm thế nào để phân phối các sự kiện bị gián đoạn?

Vì bộ hẹn giờ và các sự kiện định kỳ khác được phân phối khi bạn chạy vòng lặp chạy, việc phá vỡ vòng lặp đó sẽ làm gián đoạn việc phân phối các sự kiện đó. Ví dụ điển hình của hành vi này xảy ra bất cứ khi nào bạn thực hiện quy trình theo dõi chuột bằng cách nhập một vòng lặp và liên tục yêu cầu các sự kiện từ ứng dụng. Bởi vì mã của bạn đang nắm bắt các sự kiện trực tiếp, thay vì để ứng dụng gửi các sự kiện đó một cách bình thường, các bộ hẹn giờ hoạt động sẽ không thể kích hoạt cho đến khi quy trình theo dõi chuột của bạn thoát và trả lại quyền kiểm soát cho ứng dụng.

Điều gì xảy ra nếu bộ đếm thời gian được kích hoạt khi vòng lặp chạy đang ở giữa quá trình thực thi?

Điều này xảy ra RẤT NHIỀU LẦN, mà chúng tôi không bao giờ nhận ra. Ý tôi là chúng tôi đặt bộ hẹn giờ kích hoạt lúc 10: 10: 10: 00, nhưng đường chạy đang thực hiện một sự kiện kéo dài đến 10: 10: 10: 05, do đó bộ hẹn giờ được kích hoạt vào 10: 10: 10: 06

Tương tự, nếu bộ đếm thời gian kích hoạt khi vòng lặp chạy đang trong quá trình thực hiện quy trình xử lý, thì bộ định thời sẽ đợi cho đến lần tiếp theo thông qua vòng lặp chạy để gọi quy trình xử lý của nó. Nếu vòng lặp chạy hoàn toàn không chạy, đồng hồ sẽ không bao giờ hoạt động.

Việc cuộn hoặc bất cứ thứ gì khiến đường chạy luôn bận rộn thay đổi suốt thời gian bộ đếm thời gian của tôi sắp hoạt động?

Bạn có thể định cấu hình bộ hẹn giờ để tạo sự kiện chỉ một lần hoặc nhiều lần. Bộ hẹn giờ lặp lại tự động lên lịch lại dựa trên thời gian bắn đã lên lịch, không phải thời gian bắn thực tế. Ví dụ: nếu bộ hẹn giờ được lên lịch để bắn vào một thời điểm cụ thể và cứ sau 5 giây sau đó, thời gian bắn theo lịch sẽ luôn rơi vào khoảng thời gian 5 giây ban đầu, ngay cả khi thời gian bắn thực tế bị trì hoãn. Nếu thời gian bắn bị trì hoãn đến mức bỏ lỡ một hoặc nhiều thời gian bắn theo lịch trình, bộ đếm thời gian chỉ được kích hoạt một lần trong khoảng thời gian đã bỏ lỡ. Sau khi kích hoạt trong khoảng thời gian đã bỏ lỡ, bộ đếm thời gian được lên lịch lại cho thời gian kích hoạt theo lịch trình tiếp theo.

Làm cách nào để thay đổi chế độ của RunLoops?

Bạn không thể. Hệ điều hành chỉ tự thay đổi cho bạn. Ví dụ: khi người dùng nhấn, thì chế độ sẽ chuyển sang eventTracking. Khi người dùng nhấn xong, chế độ sẽ quay trở lại default. Nếu bạn muốn một thứ gì đó được chạy ở một chế độ cụ thể, thì bạn phải đảm bảo điều đó xảy ra.


Giải pháp:

Khi người dùng cuộn, Chế độ vòng lặp chạy sẽ trở thành tracking. RunLoop được thiết kế để sang số. Khi chế độ được đặt thành eventTracking, thì nó sẽ ưu tiên (hãy nhớ rằng chúng tôi có số lõi CPU hạn chế) để chạm vào các sự kiện. Đây là một thiết kế kiến ​​trúc của các nhà thiết kế hệ điều hành .

Theo mặc định, bộ hẹn giờ KHÔNG được lên lịch trên trackingchế độ. Họ được lên lịch vào:

Tạo bộ hẹn giờ và lên lịch cho vòng chạy hiện tại ở chế độ mặc định .

Bên scheduledTimerdưới thực hiện điều này:

RunLoop.main.add(timer, forMode: .default)

Nếu bạn muốn bộ đếm thời gian của mình hoạt động khi cuộn thì bạn phải thực hiện một trong hai thao tác sau:

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

Hoặc chỉ cần làm:

RunLoop.main.add(timer, forMode: .common)

Cuối cùng, thực hiện một trong những điều trên có nghĩa là chuỗi của bạn không bị chặn bởi các sự kiện chạm. tương đương với:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

Giải pháp thay thế:

Bạn có thể cân nhắc sử dụng GCD cho bộ đếm thời gian của mình, điều này sẽ giúp bạn "bảo vệ" mã của mình khỏi các vấn đề quản lý vòng lặp chạy.

Để không lặp lại, chỉ cần sử dụng:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

Đối với việc sử dụng bộ hẹn giờ lặp lại:

Xem cách sử dụng DispatchSourceTimer


Tìm hiểu sâu hơn từ cuộc thảo luận tôi đã có với Daniel Jalkut:

Câu hỏi: Làm thế nào để GCD (luồng nền), ví dụ như asyncAfter trên luồng nền được thực thi bên ngoài RunLoop? Sự hiểu biết của tôi từ điều này là mọi thứ phải được thực thi trong RunLoop

Không nhất thiết - mỗi luồng có nhiều nhất một vòng lặp chạy, nhưng có thể có 0 nếu không có lý do gì để phối hợp thực thi "quyền sở hữu" của luồng.

Luồng là khả năng chi trả ở mức hệ điều hành cung cấp cho quy trình của bạn khả năng phân chia chức năng của nó trên nhiều bối cảnh thực thi song song. Các vòng lặp chạy là khả năng chi trả ở mức khung công tác cho phép bạn chia nhỏ thêm một luồng đơn lẻ để nó có thể được chia sẻ hiệu quả bằng nhiều đường dẫn mã.

Thông thường, nếu bạn gửi một cái gì đó được chạy trên một chuỗi, nó có thể sẽ không có một vòng chạy trừ khi một cái gì đó gọi [NSRunLoop currentRunLoop]mà sẽ ngầm tạo ra một chuỗi .

Tóm lại, các chế độ về cơ bản là một cơ chế lọc cho các đầu vào và bộ hẹn giờ

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.