위치정보를 담당하는 locationManager 객체가 @Published로 정의되어 있다.
따라서 locationManager의 값이 변경이 되면 swiftUI는 자동으로 화면을 새로 그려낸다.
따라서 나는 ViewModel에서 locationManager의 속성 변경을 통해 UI를 갱신하도록 하였다.
locationManager 객체에는 사용자의 위치를 갱신하는 함수 locationManager(_, didUpdateLocations) 함수가 있다.
lastLocation 속성을 변경하는 동작이다. 해당 함수는 delegate 패턴으로 작성되며, 개발자는 해당 함수를 직접 호출할 수 없다.
그러면 어떻게 사용하느냐?
locationManager객체의 startUpdatingLocation() 함수를 통한 대리 호출만 가능하다.
해당 함수는 ViewModel에 정의하여, 여기서 호출이 일어나면 locationManager의 속성이 바뀌게 되고,
이를 SwiftUI가 자동으로 UI에 반영해 준다.
그리고 locationManager에는 사용자의 위치를 기반으로 주소값을 갱신하는 함수가 있다.
lastLocation을 기준으로 주소를 가져오게 된다.
이때 든 생각
어차피 따로 쓸 일 없는데, 호출 한 번으로 사용자 위치 갱신할 때 한 번에 주소도 갱신하면 안 됨?
내가 의도한 순서:
위치 갱신 -> 갱신한 위도, 경도를 기준으로 주소 갱신 -> 변경된 속성을 SwiftUI가 감지하여 UI 갱신
이런 단순한 생각으로 ViewModel에 다음과 같이 작성하였으나,
사용자 위치만 갱신될 뿐 정작 중요한 주소 레이블이 UI에 반영이 되지 않았다.
열심히 함수에 print()를 찍어 동작이 어떻게 이루어지다 파악해 보니 내가 작성한 것은 다음과 같이 동작했다.
즉 delegate 패턴으로 작성된 locationManager(_, didUpdateLocation) 함수로 넘어가기 전에 비동기로 주소갱신이 이루어진다.
당연하게도 갱신이 되지 않은 위치로 주소를 가져오니 이전 주소가 나오고,
위도, 경도 값이 경신된 이후 함수가 종료된다.
즉 locationManager의 대리호출과 비동기 호출의 조합으로 동작의 순서가 엉망이 돼버린 것.
문제 해결
짧지 않은 기간 동안의 삽질과 아직 해결되지 않은 의문점이 남아있지만 아무튼 지금은 의도된 동작대로 작동한다.
일단 위치갱신과 주소갱신을 한 번에 하겠다는 생각을 버렸다.
따라서 주소갱신함수는 locationManager의 속성을 바꾸는 것이 아닌, Sting을 반환하는 비동기 함수로 변경하였다.
* 비동기로 굳이 작성하는 이유는 주소를 반환하는 reverseDeocoderLocation() 함수가 비동기로 작성되었기 때문.
이어서 locationManager에 있던 주소 레이블 속성을 viewModel로 가져왔다.
이렇게 각 함수를 ViewModel에서 따로 호출할 수 있게 변경하였다.
이어서 View에서는 onChange를 통해 위도, 경도의 갱신이 완료되면, 주소를 가져오게 하여 동작의 순서를 정해주었다.
정리
동작을 분리한 이유는 다음 한 줄로 정리할 수 있다.
UI의 반영은 메인 스레드에서만 이루어진다.
ViewModel을 담당하는 FitcastManager가 @MainActor로 선언된 이유가 바로 여기서 작동하는 동작들은 모두 메인스레드에서 이루어지게 하기 위함이다.
따라서 기존의 방식대로 주소 갱신이 비동기로 동작하게 된다면 메인스레드가 아닌 새로 생성된 스레드에서 작동하게 되는 것이고, 여기서 이루어진 갱신은 UI에 반영될 수 없다고 판단하였다.
따라서 UI에 반영할 주소값의 갱신은 @MainActor로 선언된 ViewModel의 속성으로 두어 비동기로 작동하더라도 메인스레드에서 동작하게끔 수정해 주었다.
사실 해당 문제를 해결하기까지 아직 의문점이 남은 부분이 많이 있다. 아직 aync/await의 사용법과 MainActor와 관련된 이해도가 떨어져서 그렇다고 생각한다. 해당 개념에 대해 쉽게 설명한 글이 많으므로 나중에 완벽하게 이해된다면 다시 한번 리팩토링을 통해 후기로 돌아와야겠다.
배열의 크기 N이 최대 5000으로 작은 값에 속한다. 따라서 이분탐색으로 찾게 될 값은 구간의 최대 점수를 찾는 문제로 접근했다.
즉 이분탐색으로 mid값을 고정하고 길이 N의 배열을 순차탐색하면서 탐색 중인 구간의 최댓값 - 최솟값이 mid보다 크다면 구간을 나누어 마지막까지 탐색이 끝났을 때 구간의 개수가 M이하라면 최대 점수를 더 낮은 쪽을 탐색, 개수가 M보다 크다면 최대 점수가 더 큰 쪽으로 이분 탐색을 이어가면 된다.
입력 예제를 기준으로 동작을 그림으로 나타내면 다음과 같다. 이분탐색 mid 값이 3, 5, 4 일 경우 각각의 구간의 개수 조건을 확인. 구간의 최댓값이 5일 경우에 조건을 만족하므로 5가 정답이 된다.