[Ios App] 최적화로 로딩속도 빠르게 하기
[Ios App] 최적화로 로딩속도 빠르게 하기
서버와 연동시켜 http로 스크립트 불러오기 까지 완료했다. 근데 문제가 생김
Trouble SHooting
- 로딩이 너무느림 (ListView -> Detail Page 까지 한 30초 걸림)
- 타임스탬프 누르면 렉걸리듯이 5초정도 딜레이가 생김
아무래도 최적화 없이 기능 개발 위주로 하다 보니 렉이 걸리는것 같아서 최적화를 해보기로 한다.
디테일 페이지 로딩 및 타임스탬프 상호작용에서 성능 저하가 발생하는 이유를 생각해 봤을 때
- 스크립트 파싱이 메인 스레드에서 실행됨
- 정규식을 사용해 매번 스크립트를 파싱하거나 타임스탬프를 계산하는 작업이 메인 스레드에서 실행되면, UI 갱신과 동시에 무거운 연산이 겹쳐서 렉이 발생할 수 있다.
- 반복 계산 및 캐싱 미사용
- 동일한 스크립트에 대해 타임스탬프를 여러 번 파싱하는 경우, 매번 정규식 연산을 수행하면 불필요한 오버헤드가 발생한다.
- 네트워크 요청 및 JSON 디코딩
- 디테일 페이지를 로드할 때 네트워크 요청과 디코딩이 동기적으로 (또는 메인 스레드에 영향을 주는 방식으로) 처리된다면 느려질 수 있다.
따라서 최적화 실시
1. 비동기 처리 및 백그라운드 작업
파싱 작업 백그라운드 처리:
스크립트 파싱, 특히 정규식으로 타임스탬프를 파싱하는 작업은 메인 스레드가 아닌 백그라운드 스레드에서 처리한 후, 결과를 메인 스레드로 전달하는 방식으로 변경. 예를 들어:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
private func parseScriptInBackground(_ script: String, completion: @escaping ([(time: String, text: String)]) -> Void) { DispatchQueue.global(qos: .userInitiated).async { let lines = script.components(separatedBy: "\n") let parsed = lines.compactMap { line -> (String, String)? in guard let range = line.range(of: "\\(\\d{1,2}:\\d{2}\\)", options: .regularExpression), !range.isEmpty else { return nil } let time = String(line[range]) let text = line.replacingOccurrences(of: time, with: "").trimmingCharacters(in: .whitespaces) return (time, text) } DispatchQueue.main.async { completion(parsed) } } }
네트워크 요청도 비동기로:
이미 URLSession의 비동기 작업을 사용하고 있긴 하지만, 디테일 페이지가 로드되기 전에 스켈레톤 UI나 로딩 애니메이션을 보여서 사용자 경험을 개선할 수 있다.
2. 결과 캐싱 (Lazy Evaluation)
파싱 결과 캐싱:
스크립트 객체 내에서 파싱 결과(예: timeStampedKOR, timeStampedJPN)를 한 번 계산한 후 다시 사용하도록 캐시할 수 있다. 예를 들어, lazy 변수를 사용하면 첫 호출 시 계산되고 이후에는 캐시된 결과를 바로 반환한다.
1 2 3 4 5 6 7 8 9 10 11 12
@Model class Script: Identifiable, ObservableObject { // 기존 속성들... // lazy 변수로 한 번 계산 후 재사용 lazy var cachedTimeStampedKOR: [(time: String, text: String)] = { return self.parseScript(self.script_KOR) }() // 기존 parseScript 함수는 그대로 사용 }
타임스탬프도 동일하게 캐싱:
타임스탬프 계산 결과 역시 lazy 변수나 저장 프로퍼티에 보관하여 반복 연산을 줄일 수 있다.
3. 정규식 최적화
정규식 객체 재사용:
정규식을 매번 새로 생성하지 않고, 한 번 생성한 정규식 객체를 재사용할 수 있다면 성능 개선에 도움이 된다. 예를 들어, Script 클래스 내부에 정적 프로퍼티로 정규식 객체를 만들어 두고 사용하는 방법이 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13
static let timeRegex = try? NSRegularExpression(pattern: "\\(\\d{1,2}:\\d{2}\\)", options: []) private func parseScript(_ script: String) -> [(time: String, text: String)] { let lines = script.components(separatedBy: "\n") return lines.compactMap { line in guard let regex = Script.timeRegex, let range = line.range(of: "\\(\\d{1,2}:\\d{2}\\)", options: .regularExpression), !range.isEmpty else { return nil } let time = String(line[range]) let text = line.replacingOccurrences(of: time, with: "").trimmingCharacters(in: .whitespaces) return (time, text) } }
## 결론
- 백그라운드 작업: 파싱 및 기타 무거운 연산을 메인 스레드가 아닌 백그라운드에서 처리
- 결과 캐싱: 한 번 계산한 결과는 저장하여 재계산을 줄인다.
- 정규식 객체 재사용: 정규식 객체를 미리 생성해두고 재사용
- 타임스탬프 동작 최적화: 타임스탬프를 누를 때 실행하는 작업을 최적화하고, UI 업데이트를 최소화
- 프로파일링: Instruments 등을 사용해 실제 병목을 확인하고 개선
This post is licensed under CC BY 4.0 by the author.