[야곰아카데미] 오픈마켓

J.Noma·2022년 2월 4일
0

🌀 기본정보

🔸 1. Project Info.

🔸 2. App 구동

메인화면상품등록상품수정
메인화면상품등록상품수정

🌀 Swift 및 코딩 스타일 관련

🔸 코드배치 & 폴더분리 고민

아래와 같은 기준으로 폴더를 분리하고 View와 ViewController에 대한 코드구조를 create/organize/configure로 통일하는 시도. 프로퍼티 초기화없이 나머지를 하면 바로 exception나므로 create를 가장 먼저 해줘야 하고, addSubview없이 오토레이아웃걸면 마찬가지로 exception나므로 organize를 그 다음에 바로 해줘야 한다

🔸 공통구현 객체는 규모가 작아야 한다

상품등록 화면과 상품수정 화면은 UI가 매우 유사하므로 코드를 재사용하는 것을 고려해볼 수 있습니다. 이를 위해, 두 화면의 view를 통째로 모델링한 ProductEditingView를 만들어 서로 다른 부분은 변경이 가능하도록 인터페이스 메서드를 구현해주었습니다. 이는 요구사항이 변경되지 않는다면 괜찮은 구현일 수 있으나, 만에 하나 두 화면의 다른 부분이 늘어난다면 (특히 어느 한 화면에만 있는 UI가 발생한다면) 변경이 매우 어려운 구조라는 문제가 있었습니다

이를 해결하기 위해 한 덩어리로 된 view를 세분화해주는게 필요했습니다. 세분화 level은 세세할수록 변경에는 용이하나 재사용성은 떨어지는 부분이 있어 미래에 변경될 가능성이 적은 "적당한 수준"으로 세분화하는 것이 타당해보였습니다. NavigationBar와 ImageScrollView는 코드규모가 어느정도 있으면서 요구사항이 변경될 가능성이 낮다 판단되어 별도의 타입으로 구현해주었고 그 외 TextField/TextView는 마찬가지로 개별 UI 단위로는 변경 가능성이 낮으나 코드규모가 작아 재사용성에 따른 이득이 크지 않으므로 별도의 Custom 타입으로 구현하지 않고 UITextField/UITextView 그대로 사용하였습니다

image

🔸 Custom Type 정의 시, final 처리

Custom type을 정의하는 경우 이를 다시 상속해서 또 다른 Custom type을 만들어 쓰는 등 상속으로 인해 복잡도가 올라가 가독성이 낮아집니다. 규모가 큰 코드에서 이런 상속 구조를 파악하기 위해선 일일히 검색해야 하므로 자식 타입이 없는 경우 final 처리를 통해 수고를 덜 수 있습니다

🔸 delegate 사용 시 참조순환 주의

Grid/ListCollectionViewController는 상위 ViewController의 presentation 수행을 위해 viewPresentationDelegate라는 프로퍼티를 가집니다. 이는 서로를 참조하는 상황이므로 반드시 한 쪽은 약한참조 처리를 해주어야 참조순환 문제가 발생하지 않으므로 주의가 필요합니다

🔸 JSON Parsing 타입은 네트워크 API마다 개별적으로 둘 것

네트워크 API 요청을 사용하면 JSON 포맷의 요청/응답을 parsing하기 위해 Codable을 채택하는 타입을 설계하게 됩니다. 이 때 API 간 필요한 Parsing 타입이 겹치더라도 개별적으로 두는 것이 좋습니다. 이 부분은 이름이라던지 required 여부라던지 하는 세세한 부분이 빈번히 변경되기 때문입니다

🔸 associated type은 프로토콜에서 제네릭을 사용하기 위함입니다

제곧내

🔸 리터럴로 존재하는 상수값들은 네임스페이스로 관리해주는 방법도 있다

제곧내

🔸 lazy var는 무한루프 위험이 있다

지연초기화를 위해 사용하는 lazy var 프로퍼티가 서로를 필요로 하면 무한루프에 빠지므로 각별한 주의가 필요하다

🔸 selector 메서드 argument 전달

버튼에 addTarget을 해줄 때 objc의 selector를 등록하게 된다. selector 메서드를 호출할 때 argument를 전달할 순 없을까하는 의문이 들었다. 예로, selector가 호출된 시점에 self의 어떤 파라미터를 읽어 프린트한다던지 등의 수요가 있을 것 같다

하지만, "확실히 안된다"는 답변은 찾기 어려웠고.. 참고포스팅에 나오는 objc의 selector 사용법에 따르면 addTarget 내부에서 selector를 호출하는 구문에 argument를 넣어줘야 하므로 외부에서 코드를 짜는 우리는 조절이 불가할 것 같다는 추정이다

🌀 기술적인 이슈

🔸 서버에서 이미지 용량 제한 거는 경우

상품 등록 시 각 이미지 용량을 300KB로 제한하는 요구사항이 있었다. 이런 용량 관련 요구사항은 현업에서는 너무나 당연한 것으로 생각된다. 하지만, 이번 프로젝트를 하며 이 부분에 많은 시간을 쏟았으나 온전한 정답을 찾진 못했다. 특정 용량으로의 최적화 방법은 찾지 못했고 용량을 줄이기 위해 이미지 크기와 화질을 조절하는 방법을 학습하였다

  1. Resizing
    이미지 크기와 용량이 정비례할 것이라 생각했으나 실제론 그렇지 않았다. 수소문 끝에 알아낸 것은 이미지가 렌더링될 때 사용되는 메모리는 widht height bytesPerPixel이 맞으나, 이미지가 이미 특정 포맷으로 압축된 상태이므로 resizing하더라도 정비례하게 줄지 않는다는 것이다

  2. Compression
    jpegData()

🔸 네트워크 이미지 로딩과 Cell 재사용 이슈

URL을 기반으로 서버로부터 이미지를 받아 Cell을 채워주는 경우 재사용에 관한 이슈를 항상 염두에 두어야 합니다. 아래와 같이 이미지 로딩의 completion handler에 해당 Cell에 로딩된 이미지를 넣는 방식을 사용하게 됩니다. 만약 로딩이 완료되지 않은 상태에서 스크롤이 발생하여 다른 indexPath에 대해 해당 Cell이 재사용될 경우 원래 의도했던 indexPath가 아닌 다른 indexPath의 Cell에 해당 이미지가 업데이트 되는 문제가 발생합니다

이를 막기 위해 보통 로딩된 이미지를 UI 업데이트하기 전에 조건문을 넣어 원래 의도한 indexPath의 Cell이 아니라면 업데이트하지 않도록 하는 방식을 사용하게 됩니다. 이 조건문에는 다양한 기준이 사용될 수 있는데 indexPath가 사용될 수도 있고 아래와 같이 이미지 URL을 사용할 수도 있습니다

guard self.currentThumbnailURL == product.thumbnail else { return }

DispatchQueue.main.async {
	self.imageView.image = UIImage(data: data)
}

🔸 네트워크 로딩으로 동시에 여러 개를 업데이트하는 경우에서의 순서 관리

현재 코드에서 상품상세화면에서 상품수정으로 넘어갈 경우 URL을 기반으로 해당 상품에 대해 서버가 가지고 있는 모든 이미지를 불러와 StackView에 넣어주는 구조입니다. 이처럼 네트워크 로딩으로 여러 개의 이미지를 받아와 업데이트해주는 경우에서 요청순서와 응답순서가 다를 수 있으므로 필요하다면 순서를 지켜주기 위해 추가 처리가 필요합니다

현재 코드에선 이를 위해 이미지가 들어갈 UIImageView를 StackView에 먼저 넣어놓고 인덱스를 사용하여 로딩이 완료된 이미지를 절절한 위치의 ImageView에 넣는 방식을 사용하고 있습니다

🔸 네트워크 로딩에 Data(contentsOf: URL) 사용금지

처음에 이미지 데이터 로딩을 위해 Data(contentsOf: URL)를 사용했는데 이는 동기로 동작하기 때문에 네트워크 기반 URL에는 적합하지 않습니다. 네트워크 로딩에는 URLSession의 dataTask를 사용해야 합니다. Data(contentsOf: URL) 공식문서 참고

🔸 Dynamic Type 적용하기

dynamic type을 정상적으로 지원하려면 텍스트 자체와 텍스트를 담은 레이블 (버튼이라면 버튼까지) 모두가 dynamic type에 따라 늘어날 수 있도록 설정해줘야 합니다

  1. 텍스트 자체
    font를 textStyle로 지정하거나, 숫자값으로 지정 후 UIFontMetrics를 사용해줘야 합니다
  2. 레이블
    autoresizing까지 해제해야 늘어납니다
    label.adjustsFontForContentSizeCategory = true
    label.translatesAutoresizingMaskIntoConstraints = false
  3. 버튼
    버튼의 autoresizing도 해제합니다
    button.translatesAutoresizingMaskIntoConstraints = false

🔸 이미지 포맷 PNG와 JPEG의 차이

PNG는 무손실 압축인 반면 JPEG은 손실 압축이다. 따라서 JPEG이 화질 손실은 발생하나 압축률은 더 높다

🔸 타입프로퍼티 vs 인스턴스프로퍼티 (모든 인스턴스의 값이 같은 경우)

모든 인스턴스의 값이 같다면 타입 프로퍼티로 사용하는 것을 고려할 수 있다. 이는 메모리 관점에서 효율적이기 때문이다. 하지만, 만약 생성하는 인스턴스 수가 많지 않다면 메모리 life time 관점에서는 인스턴스와 함께 해제되는 인스턴스 프로퍼티를 사용하는게 유리할 수 있다

🔸 테스트 더블에 관하여

  • Stub
    Mock에 비해 실제 객체의 더 작은 부분만을 구현합니다. 또한, 테스트 결과를 객체의 "상태"를 확인하여 결정하는 "상태검증 테스트"에 주로 사용됩니다. 예로, 현재 저희 코드의 StubURLSession처럼 호출되면 정해진 값(HTTP status code 등)을 반환하는 테스트 더블을 만들어야 하는 경우 Stub이 사용된다고 파악했습니다
  • Mock
    실제 객체와 가장 유사한 수준으로 구현됩니다. Stub과는 달리 객체의 어떤 메서드가 호출되었는지 등의 "행위검증 테스트"에 사용됩니다.
  • Dummy
    가장 기본적인 테스트 더블로, 어떠한 기능도 구현되어 있지 않습니다. 테스트 코드에서 Dummy 객체에게는 어떠한 기능을 하길 바라지 않고 단순히 객체를 만들고, 넘기는 정도의 목적만 필요할 때 사용될 것 같습니다
  • Fake
    실제 객체의 로직을 단순화하여 구현한 테스트 더블을 말합니다. 실제 로직과는 차이가 있기에 실제 앱에서는 제대로 동작하지 않음
  • Spy
    기본적으로 Stub의 "상태검증 테스트"라는 목적을 가지면서, Mock의 "행위검증 테스트"를 위한 일부 로직(메서드 호출횟수를 기록한다던지)이 포함된 테스트 더블로 이해하였습니다.

🔸 Segmented Control로 List/Grid를 스위칭하는 경우 매번 cell을 그리지 않도록

App 메인 화면에서 상품목록을 보여주는 방식으로 List와 Grid 모두를 지원하며 segmented control로 전환하도록 구현되어 있습니다. 스위칭마다 List/Grid를 새로이 빌드하여 보여주는 것은 매우 비효율적이므로 일단 둘 다 그려놓고 하나씩 hide하는 방식을 사용하거나 혹은 (현재 코드처럼)scroll view로 왔다갔다하는 방법이 있습니다

🔸 기타

  • 시뮬레이터에서 TextField를 눌러도 키보드가 안나온다면 Xcode에서 hardware keyboard를 껐다켜주면 뜬다
  • UI 업데이트 Main 스레드에서 하는 것 까먹지 말자
  • delegate를 쓸 땐 순환참조를 주의하자
  • orientation 지원을 깜빡하는 경우가 많다

🌀 미해결 과제

🔸 Custom Cell에 contentConfiguration 적용하는 방법

Table/Collection View의 기본 style cell에 대해서는 defaultConfiguration 혹은 contentConfiguration를 사용하여 "WWDC: Modern cell configuration"에서 소개하는 방식으로 효율적으로 cell 설정이 가능합니다. 하지만 Custom Cell에 대해선 해당 WWDC 세션에서 소개되지 않아 조사가 필요합니다
참고포스팅1
참고포스팅2

🔸 UICollectionViewDiffableDataSource 사용

이번 프로젝트를 하며 CollectionView를 새로이 학습하고 구현해보았습니다. 그 과정에서 다양한 시행착오가 있었지만 가장 기억에 남는 것은 DiffableDataSource을 사용한 것입니다. 전통적인 UICollectionViewDataSource과는 달리 snapshot이라는 개념이 도입되었고 이전 snapshot과 이후 snapshot을 비교하여 변경된 부분에 대해서만 갱신을 하기에 성능면에서 우수하다는 점이 매력적이었습니다

하지만 이는 일부분이므로 WWDC: Advances in UI Data Sources 시청 필요

🔸 CollectionView Compositional Layout 사용

단순히 사용법을 익히고자 FlowLayout 대신 사용하였는데 각 방식의 장.단점 비교 필요

🔸 Pagenation 구현 필요

현재 코드는 첫 page의 20개 item만을 받아오도록 되어 있는데 pagenation을 구현하여 스크롤 다운에 반응하여 다음 페이지를 불러오도록 구현 필요

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글