안녕하세요 :) 소들입니다
오늘은 RunLoop라는 것에대해 공부를 해볼 건데여
음... 내용이 좀 어려울 수도 있어여!!
저도 오랜만에 다뤄서 완전히 이해하고 쓰는 내용이 아니라...
(한 1년 전에 공부하면서 정리했었는데 정리본이 다 날라감ㅜㅜ훠이훠이)
휴.. 뭐 포스팅 하면서 더 공부하고 그런 거 아니겠어여 ^^;
많이 공부하며 쓰지만 틀린 내용ㅇ ㅣ있을 수도 있으니
이 포스팅을 맹신하진 마시길......
모든 포스팅은 편의 말투로 합니다~!!
1. Global Thread에선 Timer가 동작하지 않는다구?
RunLoop라면서 갑자기 웬 Timer냐!!!!!!! 싶겠지만,
이 Timer가 RunLoop와 관련이 있으니까, 뭐 오프닝 같은 거라 생각해주셈 :))
자, 여러분 지금껏 타이머를 쓰면서
Global Thread에서 타이머를 돌려봤을 분이 몇 분이나 계실진 모르겠지만ㅎ..
다음과 같이 Main Thread에선
3초 뒤에 잘 동작하는 Timer가,
Global Thread에서 동작시키려고 하면,
아무리 시간이 지나도 Timer가 동작하지 않음!!
왜지???에 대한 답과
그럼 실행시킬 수 있는 방법은???에 대한 답을 찾기 위해서
오늘 RunLoop라는 것을 공부할 것임!!!!!!
오프닝 끝! 👀
2. RunLoop가 뭘까
먼저, RunLoop의 정의에 대해 알고 가보겠음
애플 문서에 나온 것을 토대로 정의 해보겠움!
RunLoop 객체는 소켓, 파일, 키보드 마우스 등의 입력 소스를 처리하는 이벤트 처리 루프로,
쓰레드가 일해야 할 때는 일하고, 일이 없으면 쉬도록 하는 목적으로 고안되었다
RunLoop 입장에서 Timer는 입력이 아닌 특수한 유형이지만, Timer의 이벤트 또한 처리한다
뭐..... 정의는 이런데, 좀 더 간단하게 설명하자면
RunLoop는 입력 소스를 처리하는 이벤트 처리 루프이고, Timer 또한 같이 처리한다는 것 같음
뭐.. 정의는 알았는데
그렇다면 이 RunLoop는 도대체 언제 어디서 어떻게 무엇에 쓰일꼬..? 👀
2-1. Thread와 RunLoop
자, 그럼 도대체 RunLoop가 어디에 사용되는지부터 보면,
Thread의 외부 입력 소스 및 Timer를 처리할 때
사용됨
자, 여기서 중요한 핵심 단어.. 바로 Thread
그럼 모든 Thread는 각자의 RunLoop를 가질 수 있냐?? 맞음!!
그럼 Thread를 생성할 때 RunLoop도 자동으로 같이 생성 되나?? 고건 아님!!
애플 문서에 보면
current 메서드를 사용할 때, 현재 쓰레드의 런루프가 없으면 생성된 후 반환된다는 걸로 보아
쓰레드가 생길 때 바로 같이 생성되는 건 아니고, 바로 다음에 공부할 내용인데
프로그래머가 해당 쓰레드에서 RunLoop의 current 메서드를 통해 런루프에 접근할 때
해당 쓰레드에 대한 런루프가 없으면, 이때 생성되는 것 같음!
아 오키오키~ current 메서드를 통해 런루프 얻었어~
그럼 RunLoop는 생성되고 나면 자동으로 실행 되나?? 이또한 아님!!
쓰레드가 각자 자기만의 런루프를 얻었다고 한들, 자동으로 실행되진 않음
RunLoop 실행에 대한 관리는 오롯이 "프로그래머"의 몫임
(Main RunLoop는 예외인데 좀이따 설명 하겠음)
따라서 만약 내가 Thread를 생성 했는데,
이 Thread가 입력 소스나 Timer를 처리해야 한다면, RunLoop를 직접 얻어서 실행시켜 주어야 함
이때, 내가 생성한 Threadㅇㅔ 대한 RunLoop를 얻으려면,
다음 메서드를 이용함
그럼 이 친구를 어떻게 사용 하냐면!!
현재 실행 중인 쓰레드 코드 내에서 다음과 같이 작성하면
let runLoop = RunLoop.current
|
현재 Thread에 대한 RunLoop를 얻을 수 있음!!!
아까 위에서 말했듯 만약 해당 쓰레드에 대한 런루프가 이미 있을 경우
그 런루프를 가져오고, 런루프가 없다면 런루프를 새로 생성해서 반환함!
근데!! 계속 말하듯이
내가 RunLoop를 얻는 것만으로는, 입력 소스 및 타이머를 처리해주진 않음
run이라는 것통해 RunLoop를 직접 실행 시켜주어야 하는데,
이것에 대해 알기 위해선 RunLoop가 어떻게 작동하는지부터 알아보잣:)
3. RunLoop의 작동 원리
RunLoop는 루프를 수행할 때, 총 2가지 Event Source를 수신함
Input Source
다른 Thread나 Application으로부터 온 비동기 이벤트를 전달한다
Timer
예약 된 시간 또는 반복 간격으로 발생하는 동기 이벤트를 전달한다
왼쪽 그림에서 노란 색 루프를 한바퀴 도는 작업이 한 번의 실행이라고 생각 했을 때,
RunLoop는 한 번의 실행 동안 내 쓰레드에 도착한 이벤트를 받고, 이에 대한 핸들러를 수행하는 객체임
루프라고 해서,
RunLoop가 자체적으로 계속 이벤트가 들어오나 안들어오나 실행을 반복 한다고 생각할 수 있겠지만,
RunLoop는 내부적으로 반복 실행을 하지 않음;;
한 번 Event Source를 읽고 전달하는 실행이 끝나면, 그대로 대기해버림;;
그 다음 Event Source가 들어와도 RunLoop는 대기 상태이기 때문에, Event를 받을 수 없는 것임
(근데 생각해보니가 Thread가 일이 없을 때 쉬게 하기 위해서니까 무한으로 돌리지 않는 게 맞을 수도..?)
따라서, 쓰레드 내에서 프로그래머가 명시적으로
for, while 등을 이용해 RunLoop를 반복 실행시켜 주어야 함
그럼 도대체 RunLoop를 어떻게 실행 시키는데?
4. RunLoop를 실행시키는 방법
자.. 여기까지 보면, 아까 Global Thread에서 왜 Timer가 실행되지 않은지에 대해
원인을 찾을 수 있음
우리는 Global Thread를 손수 생성 했고,
내가 생성한 Global Thread의 RunLoop는 실행되고 있지 않기 때문에
Timer를 작동 시켰지만, 내 Thread의 RunLoop가 이 Event를 처리하지 못해서
그래서 실행이 안 됐던 것임
그럼 실행 시키는 방법을 알기 전에,
한 가지 의문이 들어야 정상임
Main Thread도 내가 RunLoop 실행시켜준 적 없는데,
왜 Timer가 정상 동작 하는 거임?
이거에 대한 답은,
Main Thread는 애플리케이션이 실행될 때 프레임워크 차원에서 자동으로 RunLoop를 설정하고 실행
하기 때문임.. 이를 Main RunLoop라고 하는데, 때문에
우리가 Input Source, Timer Event를 아무런 RunLoop를 설정하지 않아도
Main Thread에서는 실행할 수 있던 것임
이제 왜 내가만든 Global Thread에서만 Timer가 실행되지 않은지 이유가 명확해졌음
(Timer로 국한 했지만, Socket Input, 마우스 입력 등 모든 Input Source도 마찬가지로 실행되지 않음)
자, 그럼 Global Thread의 RunLoop를 실행시키는 방법에 대해 알아보겠음
Loop를 Running 시키는 방법엔 4가지 메서드가 있는데,
4가지 모두 다뤄보겠음
4-1. run()
receiver를 영구 루프에 넣고, 이 기간 동안 모든 부착된 Input Source의 데이터를 처리한다
receiver를 parmanent loop에 넣는대;; parmanent loop가 먼데...;;
내가 공부하며 생각한 바로는,...
run()을 실행할 당시 부착되어있는 Input Source나 Timer는 영구적으로 처리
한다는 말 같음...
왜냐면 테스트 할 때
run()을 실행시키기 전에 Thread에 Timer1을 부착하고,
run()을 실행시킨 후에 Thread에 Timer2를 부착하고 테스트 해봤는데
run()을 실행시키기 전에 부착한 Timer1은 제대로 실행되고,
run()을 실행 이후에 부착한 Timer2는 실행되지 않음....
만약 내가 이해한 게 아니라면 아시는 분들 댓글좀 ㅠ
쨌든 이렇게 run을 시켜주면 Global Thread에서 Timer가 작동하긴 한다....
4-2. run(mode:before:)
루프를 한 번 실행하여, 지정된 모드에서 지정된 날짜까지 input을 blocking 한다
한국말 읽고 있는거 맞겠지 나
이거는 RunLoop의 Mode에 대해서 알아야 되는데..
이번 포스팅에선 RunLoop의 기본만 다뤄서, Mode는 다음에 다룰거라
다음에 RunLoop심화편 포스팅 할 때 추가로 다루겠음!!!
이렇게 넘어가도 되는 이유는,
실제 RunLoop를 실행시킬 땐 보통 다음에 나오는 run(until:) 메서드만 쓰기 때문임
4-3. run(until:)
지정된 날짜까지 루프를 실행하고, 그 기간 동안 루프는 부착된 모든 Input Source들의 데이터를 처리한다
보통, RunLoop를 반복 실행할 때 이 메서드를 사용함!!!
이전에 부착된 Input Source들만 영구적으로 실행되는 run()과 달리,
이 메서드는 지정된 날짜까지 루프를 실행하기 때문에, 직접 루프의 실행 시간을 지정해줄 수 있음
그리고 그 실행 시간 동안 부착된, 모든 Input Source를 처리해줌
보통 0.1 ~ 1초 정도 RunLoop를 실행시키고,
이를 원할 때까지 for, while 문으로 반복하는 형태로 작성함
다음과 같이 :)
while isRunning {
runLoop.run(until: Date().addingTimeInterval(0.1))
}
|
만약, 더이상 RunLoop가 필요 없어지면
어디선가 isRunning을 false로 해주면 RunLoop가 더이상 작동하지 않음
짜잔~~
근데, 계속 반복문을 돌려주어야 한단 점에서,
Sync하게 작동할 시 저 while문 이후의 작업은 isRunning이 false가 될 때까지 아무것도 못하잖음???
따라서 Socket 통신할 때 나는 다음과 같이 Running 작업 자체를 또 다른 Thread로 뺐ㅆ는데..
이거 완전 Swift 초보 때 짠 거라, 이게 맞는 방법인닌 잘 모르겠음..;;
4-4. acceptInput(fotMode:before:)
지정된 날짜까지 또는 지정된 모드에 대해서만 입력을 허용하여 루프를 한 번 실행한다
또 modeㄷr....
이거도 2번 메서드랑 같이 심화편 쓰면 추가하겠음
(어차피 거의 until로 사용하니까..)
쨌든, 이런 방식으로 RunLoop를 실행시킬 수 이따 :)
5. 언제 RunLoop를 사용할까
RunLoop를 직접 사용하는 경우는,
Thread를 직접 만들어서 다음과 같은 작업을 할 때임
- Input Source를 통해 다른 Thread와 통신하는 경우
- Timer를 사용해야 하는 경우
- Perform Selector Source를 사용해야 하는 경우
- 주기적인 일을 계속 수행해야 하는 경우
머 이럴 때 빼곤 RunLoop 굳이 안 써도 됩니다 ㅎㅎ;
.
.
.
하........................
몇시간째지............?......... ㅏㅎ하하하하하하ㅏ핳
오늘은 RunLoop의 기본 개념과 간단한 사용법에 대ㅐㅎ 알아봤는데
이 외ㅣ에도
RunLoop Observer나 RunLoop Mode 나
RunLoop가 Thread-Safe 하지 않다는 내용이나
더 공부할 게 많지만, 일단 여기서 끊고 가볼게요!!!!!!!
그리고 RunLoop를 이해시키기 위해 Global Thread에서 Timer를 돌렸지만,
실제로 Timer는 Main Thread에서 돌리는 게 안전하다 합니다ㅠㅠ
심화편을 쓸 수 있는 날이 빨리 오길 바라며 ㅠㅠㅠㅠ
피드백 대환영
'iOS > iOS' 카테고리의 다른 글
iOS) HTTP / HTTPS / RESTful 이 도대체 뭘까 (10) | 2020.12.04 |
---|---|
iOS) GCD (Grand Central Dispatch) (6) | 2020.12.02 |
iOS) Sync vs Async / Serial vs Concurrent (16) | 2020.12.01 |
iOS) 프로세스(Process) vs 쓰레드(Thread) (8) | 2020.12.01 |
iOS) 메모리 저장 방식 - Big Endian / Little Endian (1) | 2020.11.28 |