포스트

10주차 과제 (11)

Datasource에서 DiffableDatasource로 변경하면서 생긴 Exception이 있다.

  1. 검색 후 cell 클릭 시 발생하는 에러
  2. WishList에서 셀 삭제 시 에러 발생

이렇게 2개의 문제가 발생했다.

1. 첫번째 문제

1
*** Assertion failure in -[_UIDiffableDataSourceUpdate initWithIdentifiers:sectionIdentifiers:action:desinationIdentifier:relativePosition:destinationIsSection:], _UIDiffableDataSourceHelpers.m:504

그냥 셀을 클릭하면 이런 에러문구가 나온다.

혹시나해서 Coredata에 저장이되면서의 문제일까 싶어 주석을 달았지만 에러는 그대로 발생하는걸로 확인이 되었다.

확인해본결과 viewDiddisappear에서 발생하는것으로 보인다.

bind함수에서 문제 발생으로 추정

1
2
3
4
5
6
7
8
9
10
 override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        recentVM.getDocument()
        recentVM.$recentDocument
            .receive(on: DispatchQueue.main)
            .sink { [weak self] data in
                self?.collectionConfigureSnapshot()
            }.store(in: &cancellables)
        checkEmpty()
    }

이렇게 바꿔주니 해결이 되었다.

계속 에러가 프린트될때 collectionview쪽이 아닌 검색한 결과에대해 나온것을 보고 혹시나 해서 컬렉션뷰만 리로드 하게끔 했는데, 문제가 해결 되었다.

아무래도, 같은 정보가 그대로 있는상태 에서 그대로 또 적용을 하려다보니 duplicate가 발생한것으로 생각이 된다.

viewDidDisappear를 내가 VC LifeCycle을 고려해서 작성했던 이유는 collectioView를 화면에 가려졌을때 미리 갱신을 시키기 위함이었는데,

diffable 적용하기 전까진 bind를 해도 상관이 없었으나, diffable의 경우 Hashable 이 가장 중요했는데, 내가 그걸 망각하고 있었다.

귀찮아서 bind로 퉁치려고 했다가 예외가 발생했던 것.

May-12-2024 05-23-02

해결 완료.

그러면 searchVM.document에 대해서 removeAll()을 해서 다시 초기화 하고 bind를 트리거하면 되지 않을까? 해서 해봤는데

1
2
3
4
5
6
 override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        searchVM.document.removeAll()
        bind()
        checkEmpty()
    }

바로 여기서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let detailVC = DetailViewController()
        
        // DetailVC에 전달
        searchVM.$document
            .map{ document in
                return document[indexPath.row] // out of range error 발생
            }
            .eraseToAnyPublisher()
            .receive(on: DispatchQueue.main)
            .sink { [weak detailVC] document in
                let imageURL = URL(string: document.thumbnail)
                detailVC?.titleView.titleLabel.text = document.title
                detailVC?.titleView.authorLabel.text = document.authors.joined()
                detailVC?.imageView.imageView.kf.setImage(with: imageURL)
                detailVC?.imageView.priceLabel.text = document.price.stringValue
                detailVC?.bodyView.bodyLabel.text = document.contents
                detailVC?.wishSubject.send(document)
            }.store(in: &detailVC.cancellables)
        
        // CoreData에 등록
        searchVM.$document
            .map{  document in
                if !document.isEmpty {
                    return document[indexPath.row]
                } else {
                    return document[0]
                }
            }
            .eraseToAnyPublisher()
            .sink(receiveValue: { [unowned self] document in
                recentVM.saveDataToRecent(data: document)
            }).cancel()
        
        detailVC.modalPresentationStyle = .fullScreen
        present(detailVC, animated: true)
    }

out of range가 발생한다. 아마 화면이 사라지면서 올라오다보니. 미리 데이터를 다 지워버리게 되는꼴 그래서 document가 empty 상태가 되어버려서 문제가 발생한것이다.

이것저것 시도해봤지만 결론은

그냥 의도했던대로 CollectionView 갱신만 하자

오히려 viewwillappear에 해두니 collectionview에 셀이 추가되는것도 볼 수 있어서 더 좋은듯하다.

May-12-2024 05-45-35

viewwillappear에 적용 한 사진.

2. 두번째 문제

1
2
*** Assertion failure in -[UITableView deleteRowsAtIndexPaths:withRowAnimation:], UITableView.m:8630
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView must be updated via the UITableViewDiffableDataSource APIs when acting as the UITableView's dataSource: please do not call mutation APIs directly on UITableView.

이건 전체삭제에서는 문제가 없지만 부분삭제에서 발생한다.

Stackoverflow를 읽다가

직접적인 해답은 없었으나 힌트를 얻어서 한번 해보려고한다.

그리고 Youtube 를 보는데 snapshot을 VC에 적용해서 하길래 역시 이부분이 문제인 것 같다고 확신이 들었다.

우선 snapshot을 VC로 빼주기로 결정 var snapshot: NSDiffableDataSourceSnapshot<DiffableSectionModel, WishListModel>?

그리고 snapshot 함수도 변경해준다.

기존에는 지역변수였다면, vc전체에서 쓰는 전역변수로 바꿨다는게 가장 큰 의미.

1
2
3
4
5
6
7
8
9
10
 func configureSnapshot() {
        
        snapshot = NSDiffableDataSourceSnapshot<DiffableSectionModel, WishListModel>()
        snapshot?.deleteAllItems()
        snapshot?.appendSections([.wish])
        snapshot?.appendItems(wishVM.wishDocument)

        wishTableDatasource?.apply(snapshot!,animatingDifferences: true)
        
    }

뭔가 직접적으로 tableview에 접근하는건 tableView.deleteRows(at: [indexPath], with: .fade) 이거였는데,

지워버렸다.

처음에는 어딘지 고민을 했는데, 문제를 해결하고 보니 저부분에서 문제가 발생했을것으로 짐작하긴 했는데, 역시나였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        let deleteButton = UIContextualAction(style: .normal, title: "삭제") { (UIContextualAction, UIView, success: @escaping (Bool) -> Void)  in
            let alert = UIAlertController(title: "삭제하기", message: "정말 삭제하실 건가요?", preferredStyle: .alert)
            
            let ok = UIAlertAction(title: "OK", style: .destructive, handler: { [unowned self] _ in
                
                wishVM.deleteSelectedData(selectedCell: wishVM.wishDocument[indexPath.row])
                wishVM.wishDocument.remove(at: indexPath.row)
                
            })
            
            let cancel = UIAlertAction(title: "취소", style: .default)
            
            alert.addAction(ok)
            alert.addAction(cancel)
            self.present(alert,animated: false)
            
            success(true)
        }
        deleteButton.backgroundColor = .red
        return UISwipeActionsConfiguration(actions: [deleteButton])
    }

확인 완료

snapshot을 적용하지 않는 이유는 combine을 사용해서

1
2
3
4
5
wishVM.$wishDocument
            .receive(on: DispatchQueue.main)
            .sink { [unowned self] _ in
                configureSnapshot()
            }.store(in: &cancellables)

여기서 값이 변하면 바로 snapshot을 적용하기 떄문. 이게바로 Combine의 편리함. 쓰면 쓸수록 매력적인 녀석이다.

생각해보니 굳이 안뺐어도 크게 지장은 없을것 같다. 하지만 나중에 또 적용할지 모르는 일이 생길 수 있기에 빼둔다.

May-12-2024 04-47-10

이번에 이렇게 적용해보면서 디퍼블에대해 여러 에러도 접해보고 많이 배우게 되었다.

역시 뭐든 직접 부딪히면서 에러를 수정해야 많이 배우고 느낀다. 디퍼블 이번에 처음 사용해봤지만, 아주 기본적인건 이제 할 수 있을것같다.

아마 다듬어도 layout warning 정도만 해결하면 될 듯하다. (디자인 쓰레기)

Combine + DiffableDatasource 조합은 진짜 제대로 이해하고 사용하면 엄청 편할것같다.

진짜 끝.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.