UIScrollView
(또는 그 파생 클래스)가 스크롤하는 동안 NSTimers
실행중인 모든 항목 이 스크롤이 완료 될 때까지 일시 중지 된 것처럼 보입니다 .
이 문제를 해결할 방법이 있습니까? 스레드? 우선 순위 설정? 아무것도?
UIScrollView
(또는 그 파생 클래스)가 스크롤하는 동안 NSTimers
실행중인 모든 항목 이 스크롤이 완료 될 때까지 일시 중지 된 것처럼 보입니다 .
이 문제를 해결할 방법이 있습니까? 스레드? 우선 순위 설정? 아무것도?
답변:
쉽고 간단한 구현 솔루션은 다음과 같습니다.
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Swift 3를 사용하는 모든 사람
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
대신 첫 번째 명령 으로 호출 하는 것이 좋습니다 . 같은 일을 두 번하지 마십시오. Timer.scheduleTimer()
scheduleTimer()
스크롤하는 동안 타이머를 실행하려면 다른 스레드와 다른 실행 루프를 실행해야합니다. 타이머는 이벤트 루프의 일부로 처리되기 때문에 뷰 스크롤을 처리 하느라 바쁘면 타이머에 접근 할 수 없습니다. 다른 스레드에서 타이머를 실행하는 성능 / 배터리 패널티는이 경우를 처리 할 가치가 없을 수도 있습니다.
tl; dr runloop는 스크롤을 수행하므로 더 이상 이벤트를 처리 할 수 없습니다. 수동으로 타이머를 설정하여 runloop가 터치 이벤트를 처리 할 때도 발생할 수 있습니다. 또는 대체 솔루션을 시도하고 GCD를 사용하십시오.
모든 iOS 개발자는 반드시 읽어야합니다. 많은 것들이 궁극적으로 RunLoop을 통해 실행됩니다.
Apple의 문서 에서 파생되었습니다 .
런 루프는 이름이 들리는 것과 매우 비슷합니다. 스레드가 들어오는 이벤트에 대한 응답으로 이벤트 핸들러를 실행하기 위해 입력하고 사용하는 루프입니다.
런 루프를 실행할 때 타이머 및 기타 주기적 이벤트가 전달되기 때문에 해당 루프를 우회하면 해당 이벤트의 전달이 중단됩니다. 이 동작의 일반적인 예는 루프를 입력하고 응용 프로그램에서 이벤트를 반복적으로 요청하여 마우스 추적 루틴을 구현할 때마다 발생합니다. 코드가 이벤트를 직접 잡기 때문에 응용 프로그램이 이러한 이벤트를 정상적으로 전달하도록하는 대신 마우스 추적 루틴이 종료되고 응용 프로그램에 제어가 반환 될 때까지 활성 타이머가 실행되지 않습니다.
이것은 우리가 눈치 채지 못한 채 여러 번 발생합니다. 내 말은 타이머를 10 : 10 : 10 : 00에 실행하도록 설정했지만 runloop는 10 : 10 : 10 : 05까지 걸리는 이벤트를 실행하므로 타이머가 10 : 10 : 10 : 06에 실행됩니다.
마찬가지로, 실행 루프가 핸들러 루틴을 실행하는 도중에 타이머가 실행되면 타이머는 실행 루프를 통해 다음 번에 핸들러 루틴을 호출 할 때까지 대기합니다. 런 루프가 전혀 실행되지 않으면 타이머가 실행되지 않습니다.
이벤트를 한 번만 또는 반복적으로 생성하도록 타이머를 구성 할 수 있습니다. 반복 타이머는 실제 실행 시간이 아닌 예약 된 실행 시간에 따라 자동으로 다시 예약됩니다. 예를 들어 타이머가 특정 시간에 그리고 그 후 5 초마다 실행되도록 예약 된 경우 예약 된 실행 시간은 실제 실행 시간이 지연 되더라도 항상 원래 5 초 시간 간격으로 떨어집니다. 발사 시간이 너무 지연되어 예정된 발사 시간 중 하나 이상을 놓친 경우, 타이머는 놓친 기간 동안 한 번만 발사됩니다. 놓친 기간 동안 실행 된 후 타이머는 다음 예약 된 실행 시간으로 다시 예약됩니다.
당신은 할 수 없습니다. 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
}
반복 타이머의 경우 다음을 사용하십시오.
Daniel Jalkut과의 토론에서 더 자세히 살펴 보겠습니다.
질문 : GCD (백그라운드 스레드), 예를 들어 백그라운드 스레드의 asyncAfter가 RunLoop 외부에서 어떻게 실행됩니까? 이것에 대한 나의 이해는 모든 것이 RunLoop 내에서 실행된다는 것입니다.
반드시 그런 것은 아닙니다. 모든 스레드에는 최대 하나의 실행 루프가 있지만 스레드의 실행 "소유권"을 조정할 이유가 없으면 0을 가질 수 있습니다.
스레드는 여러 병렬 실행 컨텍스트에서 기능을 분할 할 수있는 기능을 프로세스에 제공하는 OS 레벨 어포던스입니다. 실행 루프는 단일 스레드를 추가로 분할하여 여러 코드 경로에서 효율적으로 공유 할 수있는 프레임 워크 수준 어포던스입니다.
일반적으로 스레드에서 실행되는 무언가를 디스패치 [NSRunLoop currentRunLoop]
하면 암시 적으로 생성하는 무언가가 호출되지 않는 한 runloop가 없을 것입니다 .
간단히 말해서 모드는 기본적으로 입력 및 타이머에 대한 필터 메커니즘입니다.