Post

[Ios App] 최적화로 로딩속도 빠르게 하기

[Ios App] 최적화로 로딩속도 빠르게 하기

서버와 연동시켜 http로 스크립트 불러오기 까지 완료했다. 근데 문제가 생김

Trouble SHooting

  1. 로딩이 너무느림 (ListView -> Detail Page 까지 한 30초 걸림)
  2. 타임스탬프 누르면 렉걸리듯이 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.