[iOS 8주차] 문제 해결: CoreData, NSManagedObjectContext - 임시 객체 중복

DoyleHWorks·2024년 12월 11일
0

https://github.com/SpartaCoding-iOS05-i/PokeContact/pull/12

문제 발생

#1. 텍스트가 길어지면 이름과 번호가 서로 간섭함
#2. 새 연락처를 추가하면 두 개씩 추가됨 (코드가 꼬였나?)
#3. 번호 입력란에 -를 추가할 수 없음
→ 아예 자동으로 추가되게 구현하면 좋을 것 같음

#4. TextField를 누르면 아래와 같은 오류가 뜸

문제 #1과 문제 #3은 다음에 해결하기로 함


문제 #2

새 연락처를 추가하면 두 개씩 추가됨

문제 접근 1

onSave 클로저 중복 등록 방지 코드를 추가함

MainCoordinator.swift

    func navigateToAddContact() {
        let detailCoordinator = detailCoordinatorFactory() as! DetailCoordinator
        let detailViewModel = DetailViewModel(coordinator: detailCoordinator, repository: contactRepository)
        let detailViewController = DetailViewController(viewModel: detailViewModel, mode: .add)
        
        detailViewModel.onSave = nil // onSave 클로저가 중복 등록되는 것을 방지
        detailViewModel.onSave = { [weak self] (newContact: Contact) in
            print("MainCoordinator: onSave called with new contact: \(newContact)")
            guard let self = self else { return }
            guard let fullName = newContact.fullName, let phoneNumber = newContact.phoneNumber else {
                print("Error: Contact data is incomplete")
                return
            }
            do {
                try self.contactRepository.addContact(name: fullName, phone: phoneNumber)
                print("Added contact successfully.")
                self.refreshMainView()
            } catch {
                print("Failed to add contact: \(error)")
            }
        }
        
        detailCoordinator.start(with: detailViewController)
        childCoordinators.append(detailCoordinator)
    }

문제 해결 안됨


문제 접근 2

CoreData에 Contact 데이터를 저장할 때 중복되는 연락처가 없는지 확인하게 하였다:

ContactRepository.swift

    func addContact(name: String, phone: String) throws {
    	// 중복 방지 코드 추가
        let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "fullName == %@ AND phoneNumber == %@", name, phone)
        
        let existingContacts = try context.fetch(fetchRequest)
        if !existingContacts.isEmpty {
            print("ContactRepository: Duplicate contact found, skipping addition")
            return
        }
        // 기존 코드
        let contact = Contact(context: context)
        contact.fullName = name
        contact.phoneNumber = phone
        try context.save()
        print("ContactRepository: Contact added successfully")
    }

두 개씩 추가되는 문제는 해결되긴 했는데, 여전히 addContact가 두 번 호출되는 것 같다.
ContactRepository: Duplicate contact found, skipping addition 디버깅 메시지가 출력되기 때문

그런데 이 상태로 앱을 껐다 켜보니 데이터가 제대로 저장되지 않는 것을 발견하였다.


문제 접근 3

디버깅 로그를 분석해보니 ContactRepository의 중복 확인 로직이 현재 세션의 객체를 중복으로 인식하고 있었다.
찾아보니, CoreData에서의 context 관리 상태와 x-coredata:///x-coredata:// 경로의 차이 때문이라고 한다.

문제 상황 파악

  1. DetailViewController에서 생성된 객체가 NSManagedObjectContext에 추가되었지만, 아직 저장되지 않은 상태임

    • 즉, 이 객체는 x-coredata:/// 경로를 가지고 있으며, CoreData의 "임시 객체"로 관리됨
  2. addContact에서 동일한 객체를 중복으로 인식함

    • fetchRequest는 이미 생성된 임시 객체를 반환하고, 이를 중복으로 간주하여 추가를 건너뜀
  3. MainViewController에는 새 객체가 보이는 이유:

    • NSManagedObjectContext는 저장 여부와 상관없이 관리 중인 모든 객체를 반환하기 때문

문제 해결

fetchContactsaddContact 메서드에 임시 객체를 제외하는 코드를 넣고,
didTapCreate 메서드에서는 NSManagedObjectContext.save()를 직접 호출하도록 하였다.

ContactRepository.swift

func fetchContacts() throws -> [Contact] {
    let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest()
    fetchRequest.includesPendingChanges = false // 임시 객체 제외
    return try context.fetch(fetchRequest)
}

func addContact(name: String, phone: String) throws {
    print("ContactRepository: Adding contact with name: \(name), phone: \(phone)")

    let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: "fullName == %@ AND phoneNumber == %@", name, phone)
    fetchRequest.includesPendingChanges = false // 임시 객체 제외

    let existingContacts = try context.fetch(fetchRequest)
    print("ContactRepository: Existing contacts for duplication: \(existingContacts)")

    if !existingContacts.isEmpty {
        print("ContactRepository: Duplicate contact found, skipping addition")
        return
    }

    let contact = Contact(context: context)
    contact.fullName = name
    contact.phoneNumber = phone

    do {
        try context.save()
        print("ContactRepository: Contact added successfully")
    } catch {
        print("ContactRepository: Failed to save contact: \(error)")
        throw error
    }
}

DetailViewController.swift

@objc private func didTapCreate() {
    print("DetailViewController: didTapCreate called.")
    guard let name = nameTextField.text, !name.isEmpty,
          let phone = phoneTextField.text, !phone.isEmpty else {
        showAlert(message: "All fields are required.")
        return
    }

    let contact = Contact(context: viewModel.repository.context)
    contact.fullName = name
    contact.phoneNumber = phone

    // 객체 저장
    do {
        try viewModel.repository.context.save() // NSManagedObjectContext.save()를 직접 호출해서 객체 저장
        print("DetailViewController: Contact saved in context.")
    } catch {
        print("DetailViewController: Failed to save context. Error: \(error)")
        showAlert(message: "Failed to save contact. Please try again.")
        return
    }

    onSave?(contact)
    navigationController?.popViewController(animated: true)
}

문제 해결됨


문제 #4

TextField를 누르면 아래와 같은 오류가 뜸:

  • Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
  • If you want to see the backtrace, please set CG_NUMERICS_SHOW_BACKTRACE environmental variable.

버그인 것 같음. 무시해도 괜찮다고 함.

profile
Reciprocity lies in knowing enough

0개의 댓글