UIScrollView는 스크롤이 끝날 때까지 NSTimer를 일시 중지합니다.


84

UIScrollView(또는 그 파생 클래스)가 스크롤하는 동안 NSTimers실행중인 모든 항목 이 스크롤이 완료 될 때까지 일시 중지 된 것처럼 보입니다 .

이 문제를 해결할 방법이 있습니까? 스레드? 우선 순위 설정? 아무것도?



답변:


201

쉽고 간단한 구현 솔루션은 다음과 같습니다.

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

2
UITrackingRunLoopMode는 모드 및 공개 모드이며 다른 타이머 동작을 만드는 데 사용할 수 있습니다.
Tom Andersen

GLKViewController에서도 매력적으로 작동합니다. UIScrollView가 나타날 때 controller.paused를 YES로 설정하고 설명 된대로 자체 타이머를 시작하십시오. scrollview가 닫히면 되 돌리십시오. 내 업데이트 / 렌더링 루프는 비용이 많이 들지 않으므로 도움이 될 수 있습니다.
Jeroen Bouma 2013

3
대박! 타이머를 무효화 할 때 제거해야하나요? 감사합니다
Jacopo Penzo 2015-09-02

스크롤링에는 작동하지만 앱이 백그라운드로 전환되면 타이머가 중지됩니다. 두 가지를 모두 달성 할 수있는 솔루션이 있습니까?
Pulkit Sharma

23

Swift 3를 사용하는 모든 사람

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


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

7
타이머를 runloop에 추가하고 다음 호출은 동일한 runloop에 다른 모드를 추가하기 때문에 timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)대신 첫 번째 명령 으로 호출 하는 것이 좋습니다 . 같은 일을 두 번하지 마십시오. Timer.scheduleTimer()scheduleTimer()
Accid Bright

8

예, 폴이 맞습니다. 이것은 런 루프 문제입니다. 특히 NSRunLoop 메서드를 사용해야합니다.

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

7

이것은 빠른 버전입니다.

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

6

스크롤하는 동안 타이머를 실행하려면 다른 스레드와 다른 실행 루프를 실행해야합니다. 타이머는 이벤트 루프의 일부로 처리되기 때문에 뷰 스크롤을 처리 하느라 바쁘면 타이머에 접근 할 수 없습니다. 다른 스레드에서 타이머를 실행하는 성능 / 배터리 패널티는이 경우를 처리 할 가치가 없을 수도 있습니다.


11
다른 스레드가 필요하지 않습니다 . Kashif의 최신 답변 을 참조하십시오 . 나는 그것을 시도했고 추가 스레드가 필요하지 않고 작동합니다.
progrmr

3
참새에게 대포를 쏘다. 스레드 대신 올바른 실행 루프 모드에서 타이머를 예약하십시오.
uliwitness

2
이 문제를 해결하기 위해 한 줄의 코드 만 추가하면되므로 아래 Kashif의 답변이 가장 좋은 답변이라고 생각합니다.
데미안 머피.

3

누구나 Swift 4를 사용합니다.

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

1

tl; dr runloop는 스크롤을 수행하므로 더 이상 이벤트를 처리 할 수 ​​없습니다. 수동으로 타이머를 설정하여 runloop가 터치 이벤트를 처리 할 때도 발생할 수 있습니다. 또는 대체 솔루션을 시도하고 GCD를 사용하십시오.


모든 iOS 개발자는 반드시 읽어야합니다. 많은 것들이 궁극적으로 RunLoop을 통해 실행됩니다.

Apple의 문서 에서 파생되었습니다 .

런 루프 란 무엇입니까?

런 루프는 이름이 들리는 것과 매우 비슷합니다. 스레드가 들어오는 이벤트에 대한 응답으로 이벤트 핸들러를 실행하기 위해 입력하고 사용하는 루프입니다.

이벤트 전달이 어떻게 중단됩니까?

런 루프를 실행할 때 타이머 및 기타 주기적 이벤트가 전달되기 때문에 해당 루프를 우회하면 해당 이벤트의 전달이 중단됩니다. 이 동작의 일반적인 예는 루프를 입력하고 응용 프로그램에서 이벤트를 반복적으로 요청하여 마우스 추적 루틴을 구현할 때마다 발생합니다. 코드가 이벤트를 직접 잡기 때문에 응용 프로그램이 이러한 이벤트를 정상적으로 전달하도록하는 대신 마우스 추적 루틴이 종료되고 응용 프로그램에 제어가 반환 될 때까지 활성 타이머가 실행되지 않습니다.

런 루프가 실행 중일 때 타이머가 실행되면 어떻게됩니까?

이것은 우리가 눈치 채지 못한 채 여러 번 발생합니다. 내 말은 타이머를 10 : 10 : 10 : 00에 실행하도록 설정했지만 runloop는 10 : 10 : 10 : 05까지 걸리는 이벤트를 실행하므로 타이머가 10 : 10 : 10 : 06에 실행됩니다.

마찬가지로, 실행 루프가 핸들러 루틴을 실행하는 도중에 타이머가 실행되면 타이머는 실행 루프를 통해 다음 번에 핸들러 루틴을 호출 할 때까지 대기합니다. 런 루프가 전혀 실행되지 않으면 타이머가 실행되지 않습니다.

내 타이머가 실행될 때마다 스크롤링이나 runloop를 바쁘게 유지하는 것이 있습니까?

이벤트를 한 번만 또는 반복적으로 생성하도록 타이머를 구성 할 수 있습니다. 반복 타이머는 실제 실행 시간이 아닌 예약 된 실행 시간에 따라 자동으로 다시 예약됩니다. 예를 들어 타이머가 특정 시간에 그리고 그 후 5 초마다 실행되도록 예약 된 경우 예약 된 실행 시간은 실제 실행 시간이 지연 되더라도 항상 원래 5 초 시간 간격으로 떨어집니다. 발사 시간이 너무 지연되어 예정된 발사 시간 중 하나 이상을 놓친 경우, 타이머는 놓친 기간 동안 한 번만 발사됩니다. 놓친 기간 동안 실행 된 후 타이머는 다음 예약 된 실행 시간으로 다시 예약됩니다.

RunLoops의 모드를 어떻게 변경할 수 있습니까?

당신은 할 수 없습니다. OS는 당신을 위해 스스로 변경됩니다. 예를 들어 사용자가 탭하면 모드가로 전환됩니다 eventTracking. 사용자가 탭하면 모드가로 돌아갑니다 default. 특정 모드에서 무언가를 실행하고 싶다면, 그것이 일어나는지 확인하는 것은 당신에게 달려 있습니다.


해결책:

사용자가 스크롤 할 때 Run Loop Mode는 tracking. RunLoop은 기어를 변속하도록 설계되었습니다. 모드가로 설정되면 eventTracking터치 이벤트에 우선 순위를 부여합니다 (CPU 코어가 제한되어 있음을 기억하십시오). 이것은 OS 디자이너의 아키텍처 디자인 입니다.

기본적으로 타이머는 tracking모드에서 예약되지 않습니다 . 일정은 다음과 같습니다.

타이머를 생성하고 기본 모드 의 현재 실행 루프에서 예약 합니다.

scheduledTimer아래이 수행합니다

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

스크롤 할 때 타이머가 작동하도록하려면 다음 중 하나를 수행해야합니다.

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

또는 다음을 수행하십시오.

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

궁극적으로 위의 중 하나를 수행하면 스레드가 터치 이벤트에 의해 차단 되지 않습니다 . 이는 다음과 같습니다.

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.

대체 솔루션 :

런 루프 관리 문제로부터 코드를 "보호"하는 데 도움이되는 타이머에 GCD를 사용하는 것을 고려할 수 있습니다.

반복되지 않는 경우 다음을 사용하십시오.

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

반복 타이머의 경우 다음을 사용하십시오.

DispatchSourceTimer 사용 방법 보기


Daniel Jalkut과의 토론에서 더 자세히 살펴 보겠습니다.

질문 : GCD (백그라운드 스레드), 예를 들어 백그라운드 스레드의 asyncAfter가 RunLoop 외부에서 어떻게 실행됩니까? 이것에 대한 나의 이해는 모든 것이 RunLoop 내에서 실행된다는 것입니다.

반드시 그런 것은 아닙니다. 모든 스레드에는 최대 하나의 실행 루프가 있지만 스레드의 실행 "소유권"을 조정할 이유가 없으면 0을 가질 수 있습니다.

스레드는 여러 병렬 실행 컨텍스트에서 기능을 분할 할 수있는 기능을 프로세스에 제공하는 OS 레벨 어포던스입니다. 실행 루프는 단일 스레드를 추가로 분할하여 여러 코드 경로에서 효율적으로 공유 할 수있는 프레임 워크 수준 어포던스입니다.

일반적으로 스레드에서 실행되는 무언가를 디스패치 [NSRunLoop currentRunLoop]하면 암시 적으로 생성하는 무언가가 호출되지 않는 한 runloop가 없을 것입니다 .

간단히 말해서 모드는 기본적으로 입력 및 타이머에 대한 필터 메커니즘입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.