[iOS] RunLoop란

2023. 3. 1. 19:43iOS

반응형

이번에 기능 개발중에 Timer를 받는 RunLoop로 인해 이슈가 있었던 경험이 있어서 다시금 RunLoop를 좀 더 자세히 알아보고자 블로그를 써보려고 합니다 🥲

 

RunLoop란?

RunLoop란 쓰레드와 관련되어있는 인프라의 일부인데, 입력 이벤트(키보드, 터치)들을 처리하기 위한 루프입니다. 그렇다면 RunLoop의 목적은 무엇일까요?

 

쓰레드가 필요할 때는 일을하게 하고 필요가 없을 때는 쉬게하기 위한 목적이라고 합니다! 아무래도 리소스를 효율적으로 사용하기 위한 기능(?)인 것 같습니다. 해당 RunLoop내에서 들어오는 이벤트들을 처리하고 이후에는 쉬게할 수 있으니 그런 것 같아요.

 

단, 주의할 점은 RunLoop는 자동으로 관리되는 것이 아니고 우리가 개발할 때, 적절한 RunLoop 내에서 쓰레드가 이벤트를 받을 수 있게 설계해야한다고 합니다 🤔 그리고 RunLoop는 쓰레드와 관련이 있는 친구인 것 같은데, 각각의 쓰레드 내에서 RunLoop를 가지고 있기 때문에 생성해 줄 필요는 없는 것 같습니다. (RunLoop.current를 이용해서 현재 쓰레드의 RunLoop를 가져오는 것이 가능)

 

뭐,, 자동은 아니니 관리도 해야하고 쓰레드에서 각각의 RunLoop도 가지는 것 같은데 어떤 원리인거지 헷갈리는데 한번 다음부터 알아보죠

 

RunLoop 원리

RunLoop는 크게 2가지의 이벤트를 받는다고 합니다.

  • Input Sources : 다른 쓰레드 또는 다른 Application으로부터 온 비동기 이벤트
  • Timer Sources : 예약된 시간 또는 일정 간격으로부터 발생해서 오는 동기 이벤트

RunLoop는 이름처럼 그림에서 노란색 루프동안 도착한 이벤트들(Input Sources, Timer Sources)을 받고 이에 대해 적절한 핸들러를 실행해주게 됩니다.

 

근데 여기서 궁금한 점은 위에서 ‘RunLoop가 자동으로 관리되는 것이 아니고 우리가 개발할 때, 적절한 이벤트를 받아서 처리할 수 있게 설계해야한다고 했는데 그걸 어떻게 하는데’겠죠. 우선 그 방법은 다시 살펴보고 이 말 뜻을 먼저 이해해봅시다 !

 

RunLoop가 저 노란 싸이클 내에서 계속 돌면서 실행을 하는게 아니고 적절한 수행을 하고 이후에는 쉬게됩니다. 만약 그 때, 이벤트가 들어오면 루프가 끝났으니 아무 반응이 없겠죠? 그렇기 때문에, 우리가 루프 내에서 적절히 이벤트를 받을 수 있게 설계하고 처리해야한다는 뜻입니다 !

 

아무래도 RunLoop의 목적 자체가 쓰레드를 일할 땐 일하게 하고 쉴 땐 쉬게하자는 목적이니 계속 이벤트를 수신하면서 처리하고 있으면 목적과는 반대가 되어버리기 때문에 그런 것 같습니다 🥲

 

RunLoop 실행하기

우선 혹시 globalQueue에서 Timer를 돌려보신 분 있으신가요.. 저는 소켓관련 기능을 개발하다 소켓이벤트만 받는 Queue에서 Timer를 돌려야하는 필요가 있어서 돌렸었는데 정상 동작하지 않았던 경험이 있습니다 🫠

 

아무래도 이런 이유 때문에, RunLoop를 적절하게 이벤트를 받을 수 있게 작성하라는 것이겠죠 ㅎㅎ..

 

우선 애플 공식문서를 보면 RunLoop를 실행시키기 위해 총 4가지의 메소드를 제공해주고 있는 것 같습니다. 이 중에서 자주 사용하는 것들만 우선 알아볼게요

1. run()
우선 해당 메소드를 실행시키게되면 영구적인 루프에 Receiver를 넣습니다. 그리고 들어오는 모든 Input Sources들을 처리합니다. 그러나 run()을 실행했을 때, 들어오는 이벤트가 없으면 그 즉시 종료됩니다.

그리고 주의할 점이 run()을 실행시키는 시점에 등록되어있는 Receiver들의 이벤트만 처리할 수 있습니다 !

DisaptchQueue.global().async {
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
        print("TIK TOK")
    }
    
    let currentLoop = RunLoop.current
    currentLoop.run()
    // TIK TOK (1초마다 무한 반복)

    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
        print("TIK TOK2")
    }
    // TIK TOK2는 Receiver로 등록되어있지 않아 수행 X
}

 

2. run(until: Date)

RunLoop를 지정된 시간까지 실행하고 그 동안 들어오는 Input Sources들을 처리합니다. 이전 메소드와는 다른 점이 시간을 지정해서 그 동안의 이벤트만 처리한다는 것 같네요 !

이 메소드도 역시 실행할 당시 등록되어있는 Receiver가 없으면 즉시 종료됩니다. 그리고 그 시점에 등록되어있는 Receiver들의 이벤트만 처리가능합니다.

DispatchQueue.global().async {
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
        print("TIK TOK")
    }
    
    let currentLoop = RunLoop.current
    currentLoop.run(until: Date().addingTimeInterval(5.5))
    // TIK TOK 5번 반복 후, RunLoop 종료
}

우선 기본적으로는 위와 같은 방법으로 사용이 가능한데요. 좀 다른 글을 보다보니 이런 방법으로 필요한 경우에 RunLoop를 종료시켜줄 수 있는 방법도 있는 것 같아서 설명드려볼게요~

 

isRunning이라는 변수를 만들어서 특정 필요한 시점까지 돌리고 false로 바꾸어서 RunLoop를 종료시킬 수 있는 방법입니다. 앗 그리고 while에서 반복되어서 run(_:) 메소드가 중복실행되는거 아닌가 걱정하실 수 있는데요. 0.5초뒤에 다음 구문으로 넘어가기 때문에 괜찮습니다 🙃

 

다만, 해당 현재 코드는 sync하게 작동하고 있어서 while문 뒤의 구문은 실행되지 않습니다. 그리고 만약 globalQueue처럼 concurrent가 아닌 serial인 경우는 Queue 내에서 다음 작업이 실행되지 않을 수 있기 때문에, 주의해야되겠습니다. 그래서 저는 좋은 코드인지는 약간 의문이 드네요 🤔 (저는 실제 Socket Queue에서 Timer를 돌려야했을 때, 이런 방법은 불안전해보여서 Timer는 메인쓰레드에서 돌려주었습니다 ㅎㅎ..)

 

 

오늘은 RunLoop에 대해서 알아보았습니다.

개발하면서 대부분을 메인 쓰레드에서 실행하기 때문에, RunLoop를 직접 관리하는 일은 많지 않았는데요. 그래도 역시 알아두면 유용하고 필수인 지식같습니다 !

잘못된 부분이나 궁금한 내용있으면 댓글남겨주세요 🙇‍♂️

 

 


참고문서

  • Apple 공식 문서
 

Run Loops

Run Loops Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread bus

developer.apple.com

  • Apple 공식 문서

 

 

RunLoop | Apple Developer Documentation

The programmatic interface to objects that manage input sources.

developer.apple.com

 

반응형