[트러블 슈팅] 간헐적으로 바인딩 클로저가 실행되지 않는 현상

2026. 3. 12. 12:39·내일배움캠프/Kickboard - Animality

⚠️ 문제: 간헐적으로 바인딩 클로저가 실행되지 않는 현상

간헐적으로 바인딩 동작을 정의해둔 클로저가 실행되지 않는 현상이 나타났습니다.

그때문에 의도한 동작이 제대로 실행되지 않았습니다.

 

❗️원인:  바인딩 클로저의 중복 동작 정의

처음에는 노트북이 안좋아서 시뮬레이터가 이상하게 동작한다고 생각했는데 전혀 아니었습니다..

 

MapVC와 SheetVC는 모두 LocationViewModel이라는 동일한 뷰모델을 사용하고 있었습니다.

 

그러나 뷰모델의 state가 변화했을 때 두 VC에서 취할 state와 해당 상태에서 실행할 동작은 서로 다르기 때문에

각각의 VC에서 자신이 필요한 state에 대해서만 클로저의 동작을 정의해주었습니다.

 

바로 이때문에 문제가 발생하였습니다.

 

MapVC는 viewDidLoad 시점에 일부 State에 대해서만 클로저의 동작을 정의합니다.

 

이후 지도에서 마커를 클릭하면 SheetVC를 present합니다.

 

SheetVC가 나타나며 viewDidLoad 시점에

MapVC에서 동작을 정의했던 동일한 클로저를 다시 SheetVC에서 필요한 일부 State에 대해서만 정의합니다.

 

즉, 동일한 클로저를 다른 내용으로 덮어 씌운 것입니다.

 

이후 SheetVC가 dismiss되면 다시 MapVC로 돌아갑니다.

 

하지만 클로저는 SheetVC에서 정의한 동작이 그대로 남아있으므로

제일 처음 MapVC에서 정의했던 클로저의 동작이 제대로 실행되지 않았던 것입니다.

 

 

 

글로만 보면 이해가 어려우니 코드와 함께 다시 살펴보겠습니다.

 

LocationViewModel에서는 아래와 같이 State를 정의하고 있습니다.

class LocationViewModel {
	...
    
    enum State {
    	case none
        
        // MapView에서 필요한 state
        case initialized(lat: Double, lng: Double, data: [Coordinate: AnimalType]) // (현재 위도, 현재 경도, 마커)
        case locationChanged(lat: Double, lng: Double)
        case deleteRegistration(data: [Coordinate: AnimalType])
        case newRegister(data: [Coordinate: AnimalType])
        case searched(result: [LocationInfo])
        case cancelledSearch
        case noSearchResult
        
        // SheetView에서 필요한 state
        case updateSheetAnimal([Animal[)
    }
    
    var stateChanged:((State) -> Void)? // 상태가 변화할 때마다 실행할 동작 클로저
    var state: State = .none {
    	didSet {
        	stateChanged?(state) // 상태가 변화할 때마다 클로저 실행
        }
    }
    
    ...
}

 

MapVC에서는 지도 뷰에서 필요한 7가지 State에 대하여 stateChanged 클로저의 동작을 정의하고 있습니다.

class MapViewController {
    func bindingData() {
        viewModel.stateChanged = { [weak self] state in
            guard let self else { return }
            
            switch state {
            case let .initialized(lat, lng, data): // 초기 설정
                
            case let .locationChanged(lat, lng): // 위치 이동 시

            case let .newRegister(data):

            case let .deleteRegistration(data):
                
            case let .searched(result):
                
            case .cancelledSearch:
                
            case .noSearchResult:
               
            default:
                break
            }
        }
    }
}

 

SheetVC에서는 시트 뷰에서 필요한 1가지 State에 대하여

MapVC에서 정의했던 stateChanged 클로저의 동작을 다시 정의하고 있습니다.

class PinSheetView {
    private func bindingData() {
        viewModel.stateChanged = {[weak self] state in
            switch state {
            
            case let .updateSheetAnimal(result):
                self?.animals = result
                self?.setSnapshot()
                
            default:
                break
            }
        }
    }
}

 

SheetVC에서 클로저의 동작을 새롭게 덮어씌웠기 때문에

SheetVC가 dismiss되어 다시 MapVC로 돌아갔을 때에 stateChanged 클로저의 동작은

.updateSheetAnimal State에 대한 동작만 정의된 상태입니다.

 

따라서 MapVC에서 .updateSheetAnimal을 제외한 다른 State에 대하여 클로저가 동작하지 않았던 것입니다.

 

🤔 애초에 왜 이렇게 코드를 짰는지?
: 처음 코드를 작성할 때에는 MapVC에서 State의 7개의 케이스에 대해서만 클로저의 동작을 정의했으니 SheetVC에서 클로저를 정의할 때는 기존에 정의한 7개의 케이스에 대한 동작은 남아있고 나머지 1개의 케이스에 대해서 정의한 동작이 추가되어 덮어씌워질 것이라 생각했습니다..
: (7개 동작) + (1개 동작) 이라고 생각했었는데, 사실은 (기존 7개 동작 삭제) + (1개 동작) 이었던 것입니다.
: 왜 이렇게 생각했는지는 의문입니다.....

 

☑️ 해결: (임시) 기존 동작으로의 재바인딩

프로젝트 제출 직전에 알게된 문제였으므로 구조를 건드리기엔 시간이 부족하였습니다.

 

따라서 SheetVC가 dismiss 되는 시점에 MapVC의 바인딩 함수를 다시 호출하여 재바인딩 하는 방법을 선택하였습니다.

class PinSheetView: UIViewController {
	...
    
    // SheetVC가 없어질 때
    override func viewWillDisappear(_ animated: Bool) {
    	super.viewWillDisappear(animated)
        
        guard let tabBar = presentingViewController as? UITabBarController,
              let nav = tabBar.selectedViewController as? UINavigationController,
              let mapVC = nav.topViewController as? MapViewController
        else { return }
        
        mapVC.bindingData() // 클로저 재바인딩
    }
}

 

(웬만한) 동작은 정상 작동하는 것을 확인하였습니다.

 

✅ 해결: ViewModel의 분리

위 임시 해결방법에서도 문제가 발생하였습니다.

등록된 동물 삭제 시 마커가 제대로 사라지지 않는 현상이 발견되었습니다..

 

위 방법은 'SheetVC가 dismiss되어야한다'라는 전제 조건이 따라붙습니다.

 

하지만 저희 앱에서는 sheetVC가 띄워져있는 상태에서 다른 탭으로 이동하여 다른 기능을 사용할 수 있습니다.

 

따라서 SheetVC를 띄운 상태에서 마이페이지 탭으로 이동하여 등록 동물을 삭제하면,

(SheetVC가 dismiss 되지 않았기 때문에)

MapVC는 여전히 SheetVC의 클로저 동작으로 바인딩되어있으므로 마커 삭제 기능이 제대로 수행되지 않습니다.

 

이 문제는 결국 동일한 viewModel을 공유하여 서로 다른 곳에서 뷰모델의 프로퍼티를 변경하기 때문에 발생한 것입니다.

따라서 sheetVC에서 사용하는 뷰모델과 mapVC에서 사용하는 뷰모델을 아예 분리하는 방향으로 진행하였습니다.

 

기존에는 아래와 같이 MapVC에서 사용하던 viewModel을 SheetVC의 아규먼트로 넣어주었습니다.

extension MapViewController {
    func showSheet(with coordinate: Coordinate) {
        let sheet = PinSheetView(viewModel: viewModel, coordinate: coordinate) // MapVC의 viewModel을 그대로 아규먼트로 입력
        
        if let sheet = sheet.sheetPresentationController { ... } // 시트 설정
        
        self.present(sheet, animated: true)
    }
}

 

SheetViewModel을 신규 생성하여 SheetVC의 아규먼트로 넣어주는 방식으로 변경하였습니다.

SheetVC의 viewWillDisappear에 정의했던 동작은 삭제하였습니다.

extension MapViewController {
	private func showSheet(with coordinate: Coordinate) {
    	// 시트 VM 생성
        let sheetVM = SheetViewModel(modelManager: viewModel.modelManager, coordinate: coordinate)
        let sheetVC = PinSheetViewController(viewModel: sheetVM)
        
        if let sheet = sheetVC.sheetPresentationController { ... } // 시트 설정
        
        present(sheetVC, animated: true)
    }
}

 

이후에는 의도했던 동작이 잘 실행되었습니다.

 

함수의 인자로 참조 타입의 객체를 사용하는 것이 치명적일 수 있다는 것을 몸소 배운 이슈였습니다.

'내일배움캠프 > Kickboard - Animality' 카테고리의 다른 글

[개선] SheetVC에서 push하기  (0) 2026.03.12
[의사결정 기록] User 모델 저장 방식 논의  (0) 2026.03.11
[트러블 슈팅] UICollectionView 상단에 여백 주기  (0) 2026.03.11
[트러블 슈팅] UICollectionView Cell deque시 NSAttributedString 생성 불가 현상  (0) 2026.03.11
[트러블 슈팅] UIButton의 isHighlighted 미해제  (0) 2026.03.10
'내일배움캠프/Kickboard - Animality' 카테고리의 다른 글
  • [개선] SheetVC에서 push하기
  • [의사결정 기록] User 모델 저장 방식 논의
  • [트러블 슈팅] UICollectionView 상단에 여백 주기
  • [트러블 슈팅] UICollectionView Cell deque시 NSAttributedString 생성 불가 현상
devbambu
devbambu
devbambu 님의 블로그 입니다.
  • devbambu
    devbambu 님의 블로그
    devbambu
  • 전체
    오늘
    어제
    • devBambu (21)
      • WWDC (1)
      • Swift Github (1)
        • Algorithms (1)
      • 내일배움캠프 (19)
        • Kiosk - Gacha! (4)
        • 환율 계산기 - 개인 프로젝트 (6)
        • Kickboard - Animality (9)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
devbambu
[트러블 슈팅] 간헐적으로 바인딩 클로저가 실행되지 않는 현상
상단으로

티스토리툴바