11월 23일 (화) 멘토링 - boostcampwm-2021/iOS04-OwnMyWay GitHub Wiki

진행 상황

그동안 구현한것 보여드림

피드백 1

이미지 피커는 프레젠테이션 애니메이션같은걸로 예쁘게 만들 수 있음 이전 이미지에 대한 프레임정보를 넘겨서 사진이 커지는 느낌을 줄 수 있음

라인같은 앱이 이미지가 커지면서 자연스럽게 피커로 전환되는데 그런거 만들어봐도 좋을듯

  • 질문 시간

우재님 질문

이미지 탐색?을 별도의 뷰컨으로 만들었음 풀스크린을 차지하게하려고 모달 풀 스크린을 띄웠는데 제스쳐 인식을 못함 안에 스크롤뷰가 제스쳐를 잡고있다보니 아래로 했을떄 어떻게 dismiss시킬지 생각했는데 pen 제스쳐 인식기를 넣어서 끌어내리는 애니메이션을 하려고 했는데 끌어 내리는건 성공했으나 뒷배경이 까맣게 나왔음 부모가 appear가 안되어서 그런거같은데 해결 방법이 궁금합니다.

답변

제스쳐는 그런식으로 많이 씀 제스처를 내릴떄 배경색에 대한 알파를 많이 사용함 팬제스쳐를 쓰면 그 정도를 판단할 수 있으니 시작점 기준 얼마나 내려가면 알파값을 down시키는 로직 특정 이상 내려가면 아예 dismiss 시키는 방식도 있음

프레젠테이션 스타일은 그런 형식으로도 많이 하는데 커스텀하게 만들어서 애니메이션을 주기도 함

https://github.com/michaelhenry/ImageViewer.swift

위 저장소의 리드미를 보면 이미지를 눌렀을떄 확대되는 느낌이랑 축소되는 느낌을 줄 수 있음 트랜지션 매니저를 씀 이렇게 구현하면 from에서 to로 어떻게 전환되는지를 정의할 수 있음 기존 이미지 뷰를 하나 더 얹어서 더미 이미지 뷰를 조정하는 방식으로 많이 함

dismiss할때는

이런것들

스크롤 범위를 따라가서 알파값을 조정함

오픈소스를 보면서 컨셉을 알 수 있음 어떻게 되어있는지 UIKit의 기본 구현을 따를 수 밖에 없으니 오픈소스에서 불필요한 부분, 안 이쁜 부분 잘라내면서 구현하면 좋음

컨셉은 기본적인 UIKit 관련 문서나 WWDC 참조!

현준님 질문

에러를 처리해보려고 레포지토리랑 유즈케이스랑 다 거쳐서 끌어 올렸음 뷰모델 즈음에서 올려보니까 너무 중복해서 올리는 느낌이 들어서 뷰컨까지 result 타입을 올려줬었다가 불필요한 작업이 많아보여서 에러가 발생했을떄 할 작업을 클래스 같은걸로 에러처리하는 매니저를 만들어서 담당하라고 하신대로 했음 에러 처리하는 클래스를 만들어서 넘겨주기만 하면 동일하게 처리할 수 있어보였음 뷰모델에 바인드해서 클로저를 넘겨서 호출하는 방식으로 했음 -> 이렇게 해도 될까요

홈 뷰모델을 그렇게 했습니다. 콤바인 사용도 고려해봤는데 시간때문에 이렇게 했습니다.

답변

이렇게 하기보단 실제로는 상황에 따라 다르겠지만 케이스마다 분리해야한다면

이렇게 만들어주는게 좋음


401 invalid token error networking connection error mapping error 이런 에러가 있을 수 있는데 뷰컨에서 담당하는 부분들이니 이런 처리자를 상태로 변경한 다음 상태에 대한 처리를 뷰컨에서 하는게 더 적합해보임

뷰컨에서 viewModel.fetchError를 받아서 처리할 것임

에러도 비슷하게 만들어서

// Domain
enum CommonError: Error {
    case sshowToast(text: String)
}

self.viewModel?.fetchERrorPublisher
    .receive()
    .sink {
        // 일반적인 에러 동작
        self?.errorHandler.handleError(with: error)
        // 의존성 주입 안하면 익스텐션으로 하기도 함;;
        
        // 이 상황에서 발생가능한 특수한 에러는 좀 더 

        // 뷰단에서 에러 핸들링이 많지 않음
        // 1. 토스트 띄우기
        // 2. 특수 동작
        // 3. 다이얼로그
        // 4. 강제 로그아웃
        
        // 커먼한 동작들은 객체로 감싼다고 생각!
        // 에러 발생할때마다 동작해야하니까 distinct를 걸어주기?
    }

접근 자체는 나쁘지 않음 1번 -> 에러를 핸들링한다. 2번 -> 공통 에러 로직을 추출한다 (별도 객체) 3번 -> 특수 에러 로직 처리에 대한 고민 이렇게 접근시켰을것 1번은 되었으니 (구현 방식의 차이는 제끼고) 이제 2번을 고민해야함 지금은 로그인이 없으니까 간단한데 앱 전체에 녹여야하는 공통 에러 로직이 있을 수 있으니 그런걸 어떻게 처리할지 고민해보면 좋을 것

우재님 질문

코어데이터 레포지토리 자체가 코어데이터 요청을 동기식으로 하고있음 레포지토리는 네트워크나 DB가 붙을수도 있으니 이걸 비동기로 개선하려고 준비했는데 프로젝트를 생성할때 앱딜리 자체에서 생성을 해버렸음. 레포지토리 자체에서 앱딜리에 접근해야하는데 레포지가 UIKit에 의존하는 요상한 구조가 되었음 컨텍스트를 따로 만들어야할지 이해가 부족합니다.

답변

이렇게 접근하고 있는데 의존성은 주입받으면 됨 외부에서 접근은 하면 되고, 앱딜리나 컴포지션 뭐시기에서 할 수도 있음

외부에서 받아서 외부에서 주입받으면 여기서 앱딜리를 알 필요가 없을것

앱딜리는 큰 객체고 레포지는 하위 객체임. 알지 못하게 추상화해서 주입받는 방식이 좋음 앱딜리에 있는 무언가를 꼭 해야만 한다면 조금 더 추상화시키기

class CoreDataTravelRepository {
    init(context: NSManagedObjectContext) {
    
    }
}

class Deeplink {
    func openURL(url: URL) {
        UIPplication.shared.open(url: url)
    }
}

// 딥링크는 엄청난 자식인데 의존성이 꼬임 -> 완전 슈퍼인 어플객체를 알고있으니


이런 정도의 개념도 사용함 이렇게 주입시키기도 함 만들 코드에 대한 추상화도 있지만 이런 추상화도 종종 함

모듈 만들때

class CoreDataTravelRepository {
    init(context: NSManagedObjectContext) {
    
    }
}

class Deeplink {
    func openURL(url: URL) {
        UIPplication.shared.open(url: url)
    }
}


protocol OPenerLogic {
    func open(url: URL)
}

extension AppDelegate: OpenerLogic {}
// 추상화 가능하

기능의 일부만 제공해야 한다면 위와 같이 추상화도 가능함

정말 레이지하게 되어야한다면 아래와 같이도 가능함

주입받게 바꾸는게 어렵지는 않아보임

우재님 질문

코어데이터에서 매니지드 오브젝트 타입을 레포에서 가지다가 도메인 레이어로 반환하는데 결과를 던질때 반환하는 함수를 만들었는데 travel 타입을 반환하는 거니까 도메인 레이어에 의존적인건가 헷갈립니다.

답변

전에도 설명 드렸듯이

TravelRepository는 도메인 레이어임 Travel은 엔티티임

CoreDataTravelRepository는 데이터 레이어임 TravelMO는 DTO, MO, VO등등은 데이터 레이어임

프레젠테이션 레이어에선 도메인 레이어를 알고 있고 데이터 레이어는 도메인 레이어를 알고 있음

그냥 잘 만든거임

CoreDataTravelRepository -> DataSource (Local/Remote) 이렇게까지는 할 필요 없고 TravelMO를 DomainEntity로 변환해서 넘기는게 레포지토리의 역할 맞음

struct TravelMO {
    func toDomain() -> Travel {
    
    }
}

원칙적으로 지키는게 힘들수 있는데 네트워킹 프로바이더 등은 외부에 공개가 안됨. TravelMO는 앱에서 접근할 수 없는 객체들이니까 자유롭게 만들어도 됨

재질문

Travel이 TravelMO로 바뀌어야 할 일이 있었는데 그걸 Travel의 익스텐션으로 빼면 알게되니까 TravelMO에다가 주입받는 방식으로 구현했었음

답변

그럴땐 레파지토리에서 처리할 것

도메인에는 유즈케이스도 있음. 유즈케이스에 addTravel함수가 있고 travel을 받는다고 하자 얘가 레포지토리 인터페이스를 알테니까 레포지토리의 addTravel 함수를 사용 가능함 구현체에서 Travel -> TravelMO로 뺄 수 있을 것임

레포지토리 내부에서 private 함수로 매핑한다던지 하실 것 같음

한준님 질문

네비게이션 컨트롤러를 작업하고 있었는데 백버튼에 있는 타이틀을 지우고 싶었는데 모든 하나의 네비게이션 컨트롤러를 씀에도 불구하고 매 뷰컨에서 처리해야했음 커스텀을 만들어서 쓰는게 최선일까요

답변

네비게이션 컨트롤러에서 안해도 되고 뷰컨에서 해도 됨 백버튼 아이템 자체를 바꿔주기

  • backbar button item 수정하기

거의 그렇게 하셨음. viewDidLoad에서

self.navigationItem.backBarButtonItem = UIBarButtonItem(
      title: "",
      style: .plain,
      target: nil,
      action: nil
    )

이렇게 덮어씌우기 이미지 자체는 Navigation bar configuration에서 백버튼에 대한 이미지를 설정했었음 개인적으로는 저게 가장 간단했음

백버튼 타이틀이라는 속성이 있는데 비워도 될지 모르겠으니 테스트가 필요함

청수님 질문

(대충 잘 구현했냐는 내용)

답변

컬렉션뷰 안에 컬렉션뷰를 넣거나 컴포지셔널 레이아웃으로 다르게 처리하거나 상황마다 다를 듯

인스타같은걸 만들었어야 했음 똑같이 되면 재사용이 잘 ㄹ되는데 안되면

flow layout으로 섹션 기준으로 단위를 잡았음 안에 프로필 영역을 셀 하나 이미지 셀 하나 댓글 등등 셀 하나

이렇게 구성하기도 했음 섹션 자체가 여렇게 나오게 했음

패팅 지정도 가능했음

콜렉션뷰만 쓰면 어떻게든 쓸 수 있음

퍼포먼스도 중요함 다이나믹하게 하면 발열이 생기기도 하고 느려지기도 함

얘는 속도가 빨랐음

추가적인건 인스타 피드같은걸 만들면 프리패칭 api가 있는데 셀을 로드하는 시점?이 있는데 데이터를 미리 처리한다던지 id 리스트?를 훑어봐도 어떤 문제를 해결하려고 했구나 알 수 있을 것임

igListKit

피드 리스트형 아이템은 어느 회사에서나 다룰 일이 많아서 복잡한 리스트를 구현한건 인스타 페북 핀터레스트가 유명한 어떤 고민을 했고 어떻게 해결했는지를 생각해보면 좋을듯

우재님 질문

이미지 개수를 따로 제한두지 않았음 예시로 이미지를 70몇장 넣으니까 바로 크래시가 났음 스택뷰를 써서 그런지 모든게 다 메모리에 로드되는게 아닌가 싶음

어차피 킹피셔 의존성을 분리할것 같은데 이미지 자체를 압축해서 넣거나 하는 방식을 고민했음

다른 해결책이 있을까요

답변

일단 셀이 만개라도 해도 만개를 안만듦

보여지는 개수만 만든다 쇼핑몰 보면 상품 무한대 스크롤되잖아 페이지 스크롤도 쓰는데 뭐를 무한대로 주는 경우도 있음 페이지나 사이즈 계산에서 3개?정도만 큐에 채워넣음 큐에 제약이 있다! 그런 이슈랑은 무관하다 스크롤에서 버벅이는 이슈는 cell에 대한 configure을 할텐데 레이아웃은 이미 되어있을거고 데이터를 채우는 동작을 많이 하는데 이미지는 메인쓰레드에서 하니까 느려질것임 그걸 백그라운드로 옮기는게 주요한 할 일임 웬만한게 다 백그라운드 태스크라고 생각

이미지 넣을떄 렌더링을 (요즘엔 큼 10메가) 우리가 쓸 프레임만큼 잘라서 올림

300pt 이미지뷰를 사용하면 13프로에선 900픽셀 * 900픽셀만 필요함 그정도만 해주는 처리를 하거나 정말 큰 이미지를 로드할때는 다른 이미지 로드 방식을 사용함 일부만 로딩해서 리사이징하는 방식을 사용하거나 일반적인 경우는 서버에서 다 처리해줘서 보내주니까 ㄱㅊ음

이거는 메모리 이슈를 해결하기 위한 용도고 로드가 오래걸리는걸 해결하려는 이슈임

아마 메인쓰레드를 어떤 태스크가 점유하는 문제가 아니었을까 싶네요

미디어 관련 동작을 메인쓰레드인지 백그라운드인지 확인하면 좋고 prepareForReuse에서 취소하는 방법. 이미 지나간 셀에 대한 동작은 더 이상 필요하지 않게 될 수 있음

prepareForReuse에서 컨텐츠를 띄우는 역할도 해야하지만 취소하는 역할도 해야함 Rx에도 이런 로직이 들어가있음 특정 시점에서 요청에 대한 취소도 할 수 있음을 인지하면 좋음

남은 작업?

접근성하고 UI 디테일 챙기고 등등

이제 시간이 얼마 없어서 생각해놓은건 많은데 다 못할듯ㅜ 접근성만 해도 거의 다 만들었으니 추가기능은 필요없어보임 접근성은 한국 자료가 거의 없을것 -> 질문

최종 발표 일정

다음주는 수요일에 동일하게 작업 다 끝났을테니 발표 준비나 추가로 알아두면 좋을 개발 이야기