• [iOS] Combine-Realm 에서 item 삭제시 crash 이슈 해결

    2020. 11. 19.

    by. dundin

    반응형

    Swift Combine 잘못 사용하다가 크래시 났던 케이스를 정리합니다. 

     

    문제 

    List에서 item 삭제 시 아래와 같은 에러로 크래시

    crash: Object has been deleted or invalidated." 0x0000600000c45140

    crash: Object has been deleted or invalidated." 0x0000600000c45140

     

    상황 

    ContentView 에서 viewModel 안에 있는 recordingList를 참조하고 있고, cell 도 동시에 viewModel 를 참조하고 있는 상황 

    // ContentView.swift
    @ObservedObject var viewModel = RecorderViewModel()
    ForEach(viewModel.recordingsList, id: \.id) { item in
    RecordingCell(audioPlayer: audioPlayer, item: item, viewModel: viewModel)
    }
    // RecordingCell.swift
    @ObservedObject var viewModel: RecorderViewModel
    // RecorderViewModel.swift
    class RecorderViewModel: ObservableObject {
    @Published var recordingsList: [DBRecordingItem] = []
    init() {
    let allRecordings = try! DBService.shared.allRecordingsSortedByCreatedAt()
    RealmPublishers.array(from: allRecordings)
    .sink(receiveCompletion: { _ in
    }, receiveValue: { recordings in
    print("DB has changed to: \(recordings.map(\.title))")
    self.recordingsList = recordings
    })
    .store(in: &subscriptions)
    }
    }

     

    문제 

    ContentView의 List와 Cell이 동시에 RecorderViewModel을 Observing하고 있기 때문에 viewmodel의 recordingsList 가 변경되었을 때 두번씩 호출되는 현상이 있었다. Realm에 추가될때는 크게 문제가 되지 않았지만 delete 하는 경우에 이미 삭제된 realm item에 접근하게 되면서 크래시가 발생했다. 

     

    해결

    1번처럼 되어있던 것을 2번으로 바꿔줌으로서 해결 

    더이상 recordingList를 참조하지 않고 viewModel에서 함수만 가져다 사용할 수 있게 되었다. 

    // RecordingCell.swift
    1. @ObservedObject var viewModel: RecorderViewModel
    2. var viewModel: RecorderViewModel

     

     

    +

    위에서 문제를 해결한 줄 알았지만 Realm Item을 직접 가지고 다니면서 cell에 DBItem 레퍼런스를 자꾸 주다보니 예상치 못한 곳에서 자꾸 이미 삭제된 DBItem을 호출하는 경우가 생겼다. 

     

    결국 Source of truth는 DB로 하되, DB가 바뀔 때 마다 그걸 UI 용 model로 변경해서 list에 넣어주는 식으로 변경했다. 

     

    // RecorderViewModel.swift
    class RecorderViewModel: ObservableObject {
    //AS-IS
    @Published var recordingsList: [DBRecordingItem] = []
    // TO-BE
    @Published var recordingsList: [RecordingItem] = []
    init() {
    let allRecordings = try! DBService.shared.allRecordingsSortedByCreatedAt()
    RealmPublishers.array(from: allRecordings)
    .sink(receiveCompletion: { _ in
    }, receiveValue: { recordings in
    print("DB has changed to: \(recordings.map(\.title))")
    // AS-IS
    self.recordingsList = recordings
    // TO-BE
    self.recordingList = recordings.map { $0.createRecordingItem() }
    })
    .store(in: &subscriptions)
    }
    }

     

    반응형

    댓글