[SeSAC] August 19, 2023

✔︎ 이번 주의 정리

  • TMDB Project 문제 확인
  • collectionViewLayout 잡기
  • 검색으로 영화 추천해 주기
  • 가볍게 정리해 본 TableView의 쓰임
  • DispatchGroup

 


TMDB Project 문제 확인하기

아... 사실 문제가 너무 많다... ㅜㅜ

코드 ㄹㅇ 꼴도보기싫음

그래도어쩌겟냐해야지...

 

  • 문제1: 테이블뷰는 로딩되는데 index가 업데이트되지 않는 건지 페이지가 업데이트되고 바로 클릭하면 오류가 남
    • Thread 1: "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (40) must be equal to the number of rows contained in that section before the update (20), plus or minus the number of rows inserted or deleted from that section (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). 

 

지금은 아예 스위프티제이슨을 이용한 코드에서 codable을 사용한 코드로 바꾸면서 이 오류는 사라졌다.

 

데이터 통신 중에 장르가 늦게 오면서 발생하는 문제도 codable과 completionHandler를 이용해서 해결했고!!

 

사실 너무 오래 지난 TIL을 수정해서 쓰는 거라 밍... 싶긴 하다

 

그래도 작성해 보자면, 이전에 TMDB 프로젝트에서 내가 경험했던 문제는 다음과 같다.

  1. 테이블뷰는 로딩되지만 인덱스가 업데이트되지 않는다.
    • 이 문제는 어케저케 잘 해결했다.
    • 인덱스가 업데이트 되지 않았던 이유는 데이터가 제대로 가져와지지 않은 상태에서 tableViewCell을 불러오게 되니 out of range가 떴기 때문인데, 데이터 통신 구분 내에 클로저를 넣어 해결했다.
  2. 장르 데이터를 어떻게 받아와야 할지 모르겠다.
    • 그러니까, 조금 더 풀어서 작성해 보자면 TMDB 프로젝트의 경우에는 장르를 Advanture처럼 문자열로 주는 것이 아니라, genreID라는 Int값으로 준다. (정확히는 String값이긴 하지만 아무튼 숫자로 준다.) 그럼 우리는 이 genreID 값을 데이터통신을 통해 장르API에서 받아와 장르 값을 변환한 뒤해당 tableView에 같이!! 표출해 주어야 했다.
    • 여기서 문제는 다시 발생했다.
      1. 장르 값을 어떻게 저장해야 효율적일지 모르겠다.
        1. 굳이 한 번만 사용할 장르값들을 저장해서 매번 사용하는 건 좋지 못한 것 같았다. 지금은 장르값이 크지 않으니 그래도 괜찮은 정도이지만, 만약에 그 값이 대책없이 커진다면?! 분명 곤란해질 것이다.
      2. 해당 값을 제대로 통신하여 잘 가지고 왔다고 해도 장르 요청값이 항상!!! 늦게 도착하여 구조체에 들어가지 않았다.
        1. 이 문제를 도통!!! 어떻게 해결해야 할지 몰랐다. 이번 주는 거의 이 문제로 끙끙 앓았다고 보면 된다.
        2. completionHandler... 그러니까, 내부에 @escaping keyword를 사용한 클로저를 호출하여 그 전에 해당 장르값을 받는다면? 괜찮을 것이다. 
        3. 결과적으로는 계속 클로저를 타고 타고 타고 타고 들어가야 한다. 그걸 조금 덜해 보고자 함수를 나누어 보았으나 조금 더 깔끔해 보이는지는 잘 모르겠다.... ^_^ 6

 

그래도 이 글을 처음 쓸 때... (한 3일 전?! 4일 전?)의 문제점들은 거의 해결했다.

정리된 삽질 과정은 이 아래 깃헙에 있다.

https://github.com/andwecrawl/SeSAC/tree/main/week%2004/TMDB%20Project

 

 

Recommendation API를 이용해서 트랜드 말고 추천을 해 주는 걸 따로 구현해 주면 좋을 것 같당

원래는 탭뷰로 구현하려고 했는데, RecommendationAPI가 movieID가 필요해서 첫 화면에서 영화를 고르고 그 영화의 비슷한 영화들을 추천해 주는 게 보고 싶으면 특정 버튼을 눌러서 추천 영화들을 볼 수 있게 만드는 것도 좋겠다.

헐... 근데 그 전에 DispatchQueue와 DispatchGroup에 대해서 완벽하게 이해하는 게 먼저일 것 같다. 적절한 API가 모가 잇을까...

 

 

 

 

 

CollectionView layout 잡기

    func configureCollectionViewLayout() {
        let layout = UICollectionViewFlowLayout()
        let space: CGFloat = 12
        
        // 여백을 제외한 content 전체의 가로 길이
        // 이런 식으로 가로를 잡아주면 각 핸드폰의 가로 길이를 바탕으로 Content의 가로를 잡아주므로 확장성이 좋아짐!
        let width = UIScreen.main.bounds.width - (space * 4)
        
        // itemSize를 여백에 맞춰 만들어 주기
        layout.itemSize = CGSize(width: (width / 3), height: (width / 3) * 1.23)
        
        // 위아래 양옆 여백
        layout.sectionInset = UIEdgeInsets(top: 0, left: space, bottom: 4, right: space)
        
        // 사이 여백
        layout.minimumLineSpacing = space
        layout.minimumInteritemSpacing = space
        layout.scrollDirection = .vertical

        recommendationCollectionView.collectionViewLayout = layout
    }

이러고 중요한 거!!

이 레이아웃을 viewDidLoad든 collectionView 함수 내에서 실행시켜 주든 꼭!!! 실행시켜 주는 게 중요하다.

일케 잘 잡아노혹 아니 ㄱ- 레이아웃이 안 잡히길래 보니까 실행을 안 했더라

괜히 뻘짓햇음...

 

 

이전에 잡았던 코드도 참고로 넣어두겠다.

    func setCollectionViewLayout() {
        
        // CollectionViewLayout 잡기
        let layout = UICollectionViewFlowLayout()
        let spacing: CGFloat = 6
        
        // screen 화면에서 여백을 뺀 Cell 전체의 길이
        let width = UIScreen.main.bounds.width - (spacing * 3)
        
        // Cell 하나의 가로 / 세로
        layout.itemSize = CGSize(width: width / 2, height: width * 5 / 7)
        
        // 위아래 여백
        layout.sectionInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
        
        // Cell 사이 여백
        layout.minimumInteritemSpacing = spacing / 2
        
        // layout 적용
        collectionView.collectionViewLayout = layout
    }

https://dk308c.tistory.com/12

웃긴데 찾아보다가 내 블로그 저 그림이 도움이 됐다 ㅋㅋ 하 어이없음

좀 잘 그릴걸!! 싶다가도 그래도 저게어디냐,, 싶다

 

 

 

어딘가 이상하지만 잘 뜨긴 혀요~

+) 이상한 거 해결.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ

posterView가 왜 안 뜨지 했는데 ㅠ_ㅠ imageView 아래에 잠겨서 그런 거였음 (blur가 먹어버려서...)

쩨기랄...

 

 

 

 

 

 

 

 

 

 

검색으로 영화 추천해 주기

차근차근 나눠보자.

일단 검색으로 영화를 추천해 주려면, 해당 영화를 먼저 가져와야 한다. 

 

관련 API로는 TMDB API 중...

https://developer.themoviedb.org/reference/search-movie

 

Search - Movies

Search for movies by their original, translated and alternative titles.

developer.themoviedb.org

 

요런 게 있다.

param String이 필수니까 사용자가 return 버튼을 누른다면 searchBar.text를 검색해서 해당 영화를 검색하여 아이디를 찾아야 한다.

그 다음, 해당 영화의 id를 통해 collectionView에 추천 영화 리스트를 띄우면 된다.

 

1. 사용자가 return button clicked -> searchAPI의 query값으로 searchBar.text를 넣어 요청함

2. 요청값 중 가장 처음 영화의 id를 이용하여 recommendationAPI 통신 시도함

3. 통신값을 받고서 collectionView에 화면 띄워줌

 

생각해 보니까 trendView에 캐스팅값까지 나타내는 것도 dispatchGroup으로 구현해 보면 좋을 것 같다. 걔도 일단 트랜드 값 받고 -> 장르 값 / 배우List값을 받아야 하니까!! ^_^ 

또 타고 타고 들엉갈만한 API가 뭐가 있을까!?

고민해바야댈듯...

 

 

 

 

 

 

 

가볍게 정리해 본 TableView의 쓰임

CellForRowAt, awakeFromNib, prepareForReuse의 쓰임과 호출 시점에 대해서 가볍게!! 정리해 보고자 한다.

자세한 정리는 언젠가 나중에 ~.~

 

 

 

요 화면을 로딩하게 되면,

 

 

tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:numberOfRowsInSection:)
tableView(_:cellForRowAt:)
tableView(_:cellForRowAt:)
awakeFromNib()
tableView(_:cellForRowAt:)
awakeFromNib()
tableView(_:cellForRowAt:)
awakeFromNib()
tableView(_:cellForRowAt:)
awakeFromNib()

이러한 코드가 실행된다.

 

테이블뷰가 처음 셋팅될 때 실행되는 함수들은 다음과 같다.

tableView(_:numberOfRowsInSection:)

tableView(_:cellForRowAt:)

awakeFromNib()

 

먼저, tableView(_:numberOfRowsInSection:)는 섹션에 따라 사용자가 지정한 만큼의 셀을 생성해 주는 함수이다. 요 함수는 처음 화면을 로드할 때, 딱 한 번만 실행된다!!!!

이후에 아무리 reloadData를 해도 tableView(_:numberOfRowsInSection:)는 다시 동작하지 않는다 ㅠ_ㅠ

 

그 다음으로 tableView(_:cellForRowAt:)은 각 셀마다 데이터를 입혀주는 함수로, 초기 설정을 해 줄 때나 셀을 재사용할 때 모두 호출된다. 이따 스크롤했을 때 호출되는 함수들을 볼 건데, 그때도 보게 될 친구이다. ^_^

 

awakeFromNib()은 다시 설정하지 않아도 되는 셀의 초기 디자인 설정을 미리 해 주는 함수로, 처음에 테이블뷰를 로딩할 때만 호출된다. 이후, 스크롤하면서 셀이 꾸준히 재사용된다면 awakeFromNib()이 호출되지 않는다.

 

근데 재사용하면서 데이터가 남아 있으면 어떻게 비우나요?!

 

아무리 생각해도 tableView(_:cellForRowAt:)에서 셀을 비우는 거까지 담당하기에는 너무... 과하지 않나? 싶다.

 

애플 개발자들도 글케 생각했는지 우리에게는 셀을 빨아쓰기 위한 함수가 준비되어 있다.

 

 

 

자, 그럼 위 뷰에서 그대로 스크롤 해 보자.

tableView(_:cellForRowAt:)
prepareForReuse()
tableView(_:cellForRowAt:)
prepareForReuse()
tableView(_:cellForRowAt:)
prepareForReuse()

그럼 위와 같은 화면이 디버그창에 뜬다.

따라서, 스크롤했을 때( = 셀을 재사용 시) 호출되는 함수는 다음과 같다.

tableView(_:cellForRowAt:)

prepareForReuse()

 

 

tableView(_:cellForRowAt:)은 아까 봤던 셀마다 데이터를 설정해 주는 함수이고, 아래 prepareForReuse()는 셀을 빨아쓰기 위한 함수이다.

함수 이름 그대로 셀이 재사용되기 위한 준비를 하는 함수이다.

데이터가 있는 셀이 반복되다 보면 간혹 이전에 있던 셀의 데이터가 그대로 남아 창이 이상하게 구성되는 경우가 있다.

이런 경우를 방지해 주기 위해서 prepareForReuse() 함수를 이용하여 셀을 깨끗하게 빨아주자 ^_^ b 

 

 

 

 

 

DispatchGroup

 

우리가 지금껏 네트워크 통신을 하면서 하나만 통신할 때는 DispatchGroup이라는 개념이 필요하지 않았다.

여러 개의 통신을 순서대로 혹은 조금 더 효율적으로 처리하기 위해서 직렬 / 병렬, Sync / Async 개념을 이전에 정리한 적이 있다.

 

https://dk308c.tistory.com/24

 

[SeSAC] August 15, 2023

✔︎ 오늘의 정리 tableViewController에 만들어도 외부 xib 파일을 cell로 쓴다면 nib 연결은 해야 한다 뷰는 뷰, 데이터는 데이터, 각자 자신의 기능만!!! 직렬과 병렬, 동기와 비동기 왜 UI를 mainThread에

dk308c.tistory.com

 

일단! 개념만 알아서는 실제로 활용할 수 없으니, 현재 네트워크 통신에 이용하고 있는 alamofire에 대해서 조금 알아본 뒤에 여러 개의 통신을 순서대로 처리하려면 어떻게 해야 하는지, 왜 DispatchGroup이라는 개념이 필요하게 됐는지에 대해서 생각해 보려고 한다.

 

Alamofire는 비동기로 네트워크 연동을 처리하기 때문에, 내부에 서버로부터 응답을 받을 때까지 기다리지 않는다. json으로 바로 변수값을 만들어 요청 클로저 안에서 데이터를 만들어 전달하거나 콜백 함수를 통해서 응답을 처리하지 않는 한 우리는 데이터를 제대로 받을 수 없다.

 

그러니까... 예를 들어 보자. 
TMDB Trend API에는 장르가 Int값으로 들어온다. 그렇기 때문에 장르 값을 문자열로 나타내 주려면 TMDB Genre API와 통신하여 해당 값을 가져와 주어야 한다. 

장르 API는 Tv / Movie 둘로 나뉘어져 있고, Trend API에 어느 하나만 있을 거라고 보장할 수 없으므로 장르 API를 가져온 뒤에 가져온 값에서 Trend API에게 값을 요청하려고 한다.

이 과정을 단편적으로 잘라보자면,

            self.callMovieRequest(url: URL.getGenreURL(media: .movie)) { // movie Genre 조히
                self.callTvRequest(url: URL.getGenreURL(media: .tv)) { // tv Genre 조회
                    self.callRequest(page: page) { data, genre in // Trends API 호출
                        completionHandler(data, genre)
                    }
                }
            }

다음과 같다.

 

휴! 클로저가 세 번이나 들어가지만 그래도? 이정도면 괜찮은 것 같다.

근데 만약에 가져와야 할 데이터가 엄청 많다면?!

100개의 데이터 처리를 순서대로 해야 한다면 어쩔 텐가.

100번 클로저를 타고 들어간다면 그건... 악몽일 것이다. 코드를 쓰는 나도, 읽는 다른 동료도... ... ㅠ_ㅠ

 

그래서!! 소위 말하는 콜백 지옥이 여기서 나온다.

100개면 훨씬 더 많이 타고 들어갈 것이다. 댑악 끔찍... ㄷㄷ

 

 

 

근데 이거... 지금 스레드 하나만 써서 일하고 있는 거 아닌가?

Alamofire가 비동기적으로 작동하면 뭐 하겟나. 어차피 직렬로 일할 건데.

 

우리는 조금 더 효율적으로 일해 보려구 한다.

 

늘 그렇듯 Queue라는 매니저한테 일을 노나달라고 해 보자 

 

그럼 여러 thread에게 일을 나눠줄 것이다.

 

    func callRequest() {
        TMDBManager.shared.callMovieRequest(url: URL.getGenreURL(media: .movie)) {
            print("======1======")
        }
        
        TMDBManager.shared.callTvRequest(url: URL.getGenreURL(media: .tv)) {
            print("======2======")
            
        }
        
        TMDBManager.shared.callRequest(page: 1) { data, genre in
//            completionHandler(data, genre)
            print("======3======")
        }
        
        print("=======END=======")
    }

 

3개의 함수로 묶여 있던 걸 이런 식으로 풀어쓸 수 있을 것이다.

어라? 근데 이렇게 하면 문제가 생긴다.

나는 task 1 -> 2 -> 3 순서로 일하고 싶은데 일단 함수는 순서대로 실행되고, 요청은 재각각 오면서 젤 짧은 2가 먼저 요청이 끝나서 nil이 나오고? 1은 이미 끝난 2에게 데이터를 던져주려고 하고 있다 ... ...

응답 속도는 매번 달라질 수 있으니 그 순서는 매번 달라져서 가끔은 데이터가 잘 오기도 할 것이다.

 

근데 우리는 매번!!! 잘 받고 싶은 거 아닌가... ... ㅠ_ㅠ

 

그러려면 Concurrent / Async... ... 비동기적으로, 병렬적으로 보낸 일들을 명확히 순서대로 실행될 수 있도록 해야 한다. 매번 끝나는 순서를 알아서!!

 

그ㅜ래서... dispatchGroup이 필요한것이다. 

대충개념은 알겟고 enter() leave()도 알겟는데 공부하다보니까 벌써두시반이다 . . .ㅁ ㅊ거아님??

어제도 디스패치그룹 공부햇는데 ㅜㅜ ㅇ,ㄴ 좀 코드 엄청 써보구 예제도 많이 만들어 봐야 명확히 이해가 댈 거 같다...

그러고 나면 걍 TIL에 말고 디스패치그룹 글을 하나 파고싶음...

힘내보자

근데지금은말만꺼내고너무졸려서자세한내용은내일하는걸루

 

 

 

 

 

 

 

이번 TIL은 Today I Learned가 아니라 Thisweek I Learned가 아닐까 싶다 ㅋ.ㅋ

넘 ...... 넘 정신없었음 ㅠ_ㅠ!!!!

데이터 통신 혼자 한참을 헤맨 것 같다

특히 클로저!!!!!

뭔가 처음 json으로 데이터를 다룰 때까지는 흠. 갠찬군. 햇는데 막상 구조체 만들어서 codable로 부르고, completionHandler... 그러니까, 클로저 이용해서 부르고 부르고 부르고 소위 말하는 콜백 지옥을 감당할 수가 없었다

이..... 이게머꼬

 

구래도 지금은 좀 해결했다

 

너무졸려

'TIL' 카테고리의 다른 글

[SeSAC] August 27, 2023  (1) 2023.08.28
[SeSAC] August 21, 2023  (2) 2023.08.22
[SeSAC] August 15, 2023  (3) 2023.08.16
[SeSAC] August 13, 2023  (0) 2023.08.14
[SeSAC] August 10, 2023  (0) 2023.08.11