Skip to content

elisha0103/BookSearchApp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 

Repository files navigation

BookSearchApp

프로젝트 소개


프로젝트 목표

  • Open Library Search API를 사용해 책을 검색하기
  • Open Library Covers API를 사용해 책 Cover 이미지 나타내기
  • Pagination 사용하여 무한 스크롤 사용해보기
  • Covers API로부터 받아온 이미지를 기기 메모리 / 디스크 캐시하여 네트워크 자원 절약하기, 빠른 응답속도 구현하기

개발 환경

  • Deployment Target: iOS 14.1
  • Architecture: MVVM, Singleton

Foldering

BookSearchApp
├── App
├── View
│   ├── CustomUI
│   ├── BooksSearchView
│   └── BookDetailView
├── Extension
├── Architecture
│   ├── ViewModel
│   └── Singleton
├── Model
├── Preview Content
└── Preview Content
BookSearchAppTests
BookSearchSlowTests
BookSearchAppUITests

Feature-1. 검색화면 구현

주요 기능

  • 사용자가 TextField에 입력한 Text를 바탕으로 검색 결과를 받아옴
  • URLSession을 통해 네트워크 통신 구현 (테스트 코드를 통해 실행)
  • Pagination을 통해 스크롤을 화면 최하단으로 내리면 다음 데이터를 받아옴(추가 데이터가 없으면 반환 안함)
  • URLSession을 통해 네트워크 통신이 되는동안 로딩 View를 구성함

Feature-2. Cover Image 불러오기 구현

주요 기능

  • 사용자가 TextField를 통해 검색된 도서에 관한 Cover이미지를 Covers API를 통해 데이터를 받아옴
  • 최초 API를 통해 받아온 이미지 데이터를 메모리 / 디스크에 데이터 저장
  • 이미지를 다시 호출할 때, 메모리로부터 데이터를 요청 -> (실패) -> 디스크로부터 메모리 요청 -> (실패) -> API를 통해 데이터 요청
  • 검색 View에서 DetailView로 View가 이동할 때, DetailView에 보여지는 이미지도 이미지 캐싱을 통해 로드

Cache 구현 결과 (이미지 응답 5000 ~ 10000배 빠른 성능향상)

이미지로드(네트워크) 이미지로드(캐시)
Network Image Load NSCache Image Load
수행시간: 약 1초 수행시간: 약 0.0001초 ~ 0.0002초

구현 화면

검색, 상세 화면 Pagination 구현 화면

Troubleshooting

View 관련

  • 문제: LazyVGrid 각 아이템 내 VStack(alignment: .leading) 정렬이 적용되지 않음 (일부 아이템 책 제목, 책 cover Image 정렬이 일치하지 않음)

  • 원인: LazyVGrid에 각 아이템(셀)별로 부여되는 컨테이너 영역의 정렬이 필요


  • 해결: VStack의 ViewBuilder 정렬 값뿐만 아니라 컨테이너 .frame에도 정렬 수정자 적용
    .frame(maxWidth: .infinity, alignment: .leading)
    

  • 문제: ContentView에서 NavigationLink로 DetailView로 이동한 경우, DetailView에서 가지고 있는 프로퍼티들이 State 관리가 되지 않음

  • 원인: DetailView의 프로퍼티들이 State 변수로 선언되지 않음

  • 해결: DetailView의 프로퍼티들을 바인딩 프로퍼티 래퍼를 사용하여 선언하고, NavigationLink로 DetailView로 연결시켜줄 때, ViewModel의 상태 프로퍼티들을 DetailView의 프로퍼티들과 바인딩해줌


Cache 관련

  • 배경: 이미지 캐시에 대한 Singleton 객체를 만들게 되면 여러 스레드에서 한 개의 객체에 NSCache 작업을 수행하게 되어 크래시가 발생할 것을 우려하여 각 이미지 View Struct 안에 MVVM형식(데이터 바인딩)을 취하는 방식으로 이미지 캐시 구현 시도

  • 문제: 구조체 View안에서 NSCache가 제대로 저장되지 않는 문제

  • 원인: NSCache가 사용되는 구조체 View가 LazyVGrid에 사용되는 View이고, NSCache 는 캐시 삭제시 NSDiscardableContent 프로토콜을 따르므로, 사용되지 않는 Content(LazyVGrid에서 보여지지 않는 View)에 해당하는 메모리는 자동 삭제 됨

  • 해결: CacheManager 클래스를 싱글턴 아키텍처 적용하여 한 객체에서 NSCache 관리되도록 구현


  • 문제: 서버의 리소스 이름은 같지만 리소스 자체가 변경된 경우, 앱은 갱신되지 않은 캐시 데이터를 리소스로 활용

  • 해결: 서버의 리소스가 변경된 경우, 앱의 캐시 데이터도 서버의 새로운 데이터로 갱신하기 위해 기존 이미지 캐시에 Etag 개념 적용

  • 결과: Etag 이미지 캐시 사용결과 네트워크 트래픽 약 50% 감소 (150여건 검사 결과)

모든 이미지를 일괄 다운로드할 때의 네트워크 사용량 Etag 일치 유무에 따른 다운로드 네트워크 사용량

  • 문제: 이미지 캐시에 Etag 검사 로직을 적용해서 네트워크 사용량은 감소하지만, 캐시의 속도 향상이라는 최대 장점이 사라짐

  • 원인: 모든 파일에 Etag 검사를 진행하기 때문에 캐시 데이터를 가져오더라도 속도의 차이를 체감하기 힘듦


  • 해결: 각 이미지별 Etag 검사 주기를 설정 → Etag 검사 일자를 Etag와 함께 Userdefaults에 저장 (리팩토리 진행중 2023.08.24~)

    • 검사일자로부터 기준시간이 지나면 Etag 검사를 서버에 요청하여 데이터 동기화
    • 기준시간이 지나지 않으면 캐시 경로의 데이터 사용

URLSession 관련

  • 배경: API로부터 URLSession으로 이미지 로드가 미완료된 항목에 대해 중복된 요청이 발생될 수 있음

  • 문제: 문제: URLSessionTask에 이미 요청된 작업 중 동일한 요청인지 먼저 비교하고 API에 데이터 로드를 할 수 없는 문제

  • 원인

      1. URLSession으로 GET 데이터를 요청할 때, 비동기적으로 작업이 요청되고, URLSessionTask의 객체를 반환하는 URLSession.getAllTasks 함수도 비동기적으로 작업을 수행하기 때문에 sink가 맞지 않아 제대로 된 작업이 이뤄지지 않음
      1. URLSession의 기존 작업을 모두 취소하고 재요청을 보낼 수는 있지만, 이는 URLSessionTask Queue에 동일한 이미지 로드 요청건이 존재하지 않을 뿐이고 중복된 요청 자체를 API에 하지 않는 목적과는 다른 해결방법임
      1. URLSessionTask는 동일한 URL에 대한 과제일지라도, 과제에 대한 객체는 서로 다르기 때문에 비교대상이 될 수 없음
  • 해결: CoverImageViewModel을 구현하여 CoverImageView에 대한 ViewModel 객체를 개별적으로 생성하고 ViewModel의 loadingState flag 변수를 사용하여 이미 API 함수가 호출된 경우 이미지 로드가 미완료 된 View에 한 해서는 중복된 요청 자체가 발생되지 않도록 함


참고사항

코드 컨벤션
  • Swiftlint 적용

  • 네이밍

    • 일반변수 / 상수인 경우 따로 접두사를 붙이지 않는다.
    • enum case는 대문자로 시작한다.
    • 일반적인 부분이 앞에, 구체적인 부분을 뒤에 둬 모호함을 없앤다.
    • 클래스 함수에는 되도록 get을 붙이지 않는다.
    • 액션 함수는 ‘주어 + 동사 + 목적어’ 형태를 사용한다.
    • 약어로 시작하는 경우 소문자로 표기하고, 그 외 경우에는 항상 대문자로 표기한다.
    • 디자인 컨셉을 통일하고 진행했으면 전체적인 디자인을 구성하는데 효율적일거 같다.
  • 기타

    • 클로저 정의시 파라미터에는 괄호를 사용하지 않는다.
    • 클로저 정의시 가능한 경우 타입 정의를 생략한다.
    • 사용하지 않는 파라미터는 삭제하거나 _를 사용해 표시한다.
    • 구조체 생성시 Swift 구조체 생성자를 사용한다.
    • Array, Dictionary<T: U> 보다는 [T], [T: U]를 사용한다.
    • 언어에서 필수로 요구하지 않는 이상 self는 사용하지 않는다.
    • 프로퍼티의 초기화는 가능하면 init에서 하고, unwrapped Optional의 사용을 지양한다.
    • 더이상 상속이 발생하지 않는 클래스는 항상 final 키워드로 선언한다.
    • switch - case 에서 가능한 경우 default를 사용하지 않는다.
    • return은 사용하지 않는다.
    • 사용하지 않는 코드는 주석 포함 모두 삭제한다.
Git 컨벤션
  • Feat: 새로운 기능을 추가하 경우
  • Fit: 버그를 고친경우
  • Design: 사용자 UI 디자인 추가 및 변경
  • Style: 코드 포매 변경, 세미 콜로 누락, 코드 수정이 없는 경우
  • Refactor: 프로덕션 코드 리펙토링
  • Comment: 필요한 주석 추가 및 변경
  • Documents: 문서를 수정한 경우
  • Test: 테스트 추가, 테스트 리펙토링
  • Rename: 파일 혹은 폴더명을 수정하거나 옮기는 작업만 한 경우
  • Remove: 파일으 삭제한 작업만 수행한 경우

DataTable

활용기술

Platforms


Language & Tools


보완할 점

  • 사용자 검색 요청시 debounce 적용하여 연속 검색을 방지
  • UI 테스트 코드 작성 (자동화)
  • 네트워크 추상화 계층 일치
  • 중복된 이미지 로드 방지
  • 검색 하위 별로 Sub ViewModel로 상태 객체 관리
  • 네트워크 테스트코드 Mock 데이터 추가, URLSession Testable로 객체 수정
  • 이미지 캐시 로직에 ETag 확인 과정 추가

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages