[Swift] CoreData 사용해보기

팔랑이·2024년 7월 18일
0

iOS/Swift

목록 보기
51/71
post-thumbnail

이번 개인 과제에서 가장 어려웠던 것은 CoreData를 다루는 것이었다.
그래서 한번 쭉 정리를 해보려고 한다...

사실 CRUD 코드는 강의로 들었던 것의 거의 복붙이지만
이걸 다른 파일에서 적용해서 불러오는 것에 시간이 꽤 걸렸음...

CoreData에 대한 간단한 설명은 여기


CoreData 설정 및 초기화

CoreData를 사용하려면 먼저 NSPersistentContainer를 초기화해야 한다. 이 과정은 CoreData 스택을 설정하는 데 필요한 작업을 포함한다.

import Foundation
import CoreData

class CoreDataManager {
    static var shared: CoreDataManager = CoreDataManager()
    
    var container: NSPersistentContainer!

    init() {
        // "PokemonContact"라는 이름으로 NSPersistentContainer 초기화
        container = NSPersistentContainer(name: "PokemonContact")
        
        // 영구 저장소 로드
        container.loadPersistentStores { (description, error) in
            if let error = error {
                fatalError("Failed to load Core Data stack: \(error)")
            }
        }
    }
}

데이터 생성 (Create)

CoreData에 새로운 데이터를 추가하는 방법이다.
데이터를 추가할 때는 엔티티를 생성하고, 속성 값을 설정한 뒤, 컨텍스트를 저장하면 된다.

// Create
func createData(name: String, num: String, img: String) {
    // PokemonContact 엔티티 설명 가져오기
    guard let entity = NSEntityDescription.entity(forEntityName: PokemonContact.PokemonEntity, in: self.container.viewContext) else {
        return
    }
    // 새 NSManagedObject 생성
    let newContact = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
    
    let id = UUID()
    // 속성 값 설정
    newContact.setValue(id, forKey: "PokemonContact.Key.id")
    newContact.setValue(name, forKey: PokemonContact.Key.name)
    newContact.setValue(num, forKey: PokemonContact.Key.num)
    newContact.setValue(img, forKey: PokemonContact.Key.img)

    do {
        // 컨텍스트 저장
        try self.container.viewContext.save()
        print("문맥 저장 성공")
    } catch {
        print("문맥 저장 실패")
    }
}

데이터 읽기 (Read)

CoreData에서 데이터를 읽을 때는 NSFetchRequest를 사용한다.
추가로 데이터 정렬은 NSSortDescriptor를 사용하여 쉽게 설정할 수 있다. XCode에서 자체지원하는 데이터 관리 프레임워크니까... 정렬메서드는 당연히 있겠거니 해서 찾아봤는데 아주 편리한 방법으로 있었음.

// Read
func readData() -> [PokemonContact] {
    // PokemonContact 엔티티에 대한 fetch request 생성
    let fetchRequest: NSFetchRequest<PokemonContact> = PokemonContact.fetchRequest()
    // 이름 기준으로 오름차순 정렬 설정
    let sortDescriptor = NSSortDescriptor(key:"name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        // fetch request 실행
        let contacts = try self.container.viewContext.fetch(fetchRequest)
        return contacts
    } catch {
        print("데이터 읽기 실패)")
        return []
    }
}

데이터 업데이트 (Update)

데이터를 업데이트할 때는 특정 조건에 맞는 데이터를 먼저 검색한 후, 해당 데이터를 수정하고 저장하면 된다.
처음엔 단순하게 이름으로 했다가... 나중에 이름이 똑같은 엔티티들의 데이터가 수정될때 다같이 바뀌어 버리는 것을 보고 id 속성을 추가했다.

// Update - id로 필터링되도록 변경
func updateData(id: UUID, currentName: String, updateName: String, updateNum: String, updateImg: String) {
    // 특정 id를 기준으로 fetch request 생성
    let fetchRequest = PokemonContact.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: "id == %@", id as CVarArg)
    do {
        // fetch request 실행
        let result = try self.container.viewContext.fetch(fetchRequest)

        for data in result as [NSManagedObject] {
            // 데이터 업데이트
            data.setValue(updateName, forKey: PokemonContact.Key.name)
            data.setValue(updateNum, forKey: PokemonContact.Key.num)
            data.setValue(updateImg, forKey: PokemonContact.Key.img)
        }
        // 컨텍스트 저장
        try self.container.viewContext.save()
        print("데이터 수정 성공")
    } catch {
        print("데이터 수정 실패")
    }
}

참고로 UUID란
UUID는 고유한 식별자를 생성하기 위해 사용되며, UUID() 를 호출하면 새로운 고유 식별자가 생성된다.

데이터 삭제 (Delete)

데이터를 삭제할 때도 특정 조건(여기선 id)에 맞는 데이터를 먼저 검색한 후, 해당 데이터를 삭제하고 저장하면 된다.

// Delete - id로 필터링되도록 변경
func deleteData(id: UUID) {
    // 특정 id를 기준으로 fetch request 생성
    let fetchRequest = PokemonContact.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: "id == %@", id as CVarArg)

    do {
        // fetch request 실행
        let result = try self.container.viewContext.fetch(fetchRequest)
        for data in result as [NSManagedObject] {
            // 데이터 삭제
            self.container.viewContext.delete(data)
        }
        // 컨텍스트 저장
        try self.container.viewContext.save()
    } catch {
        print("데이터 삭제 실패")
    }
}

여기서부터는 컨테이너 초기화나 CRUD 외에 개발하면서 필요해서 작성했던 메서드들.

데이터 로그 (Log)

디버깅할 때 데이터가 찍히는지 안찍히는지, 어떻게 찍히는지 로그가 필요해서 logAllData() 메서드를 생성했다.

// log
func logAllData() {
    // 모든 PokemonContact 데이터 fetch request 생성
    let fetchRequest: NSFetchRequest<PokemonContact> = PokemonContact.fetchRequest()
    do {
        // fetch request 실행
        let contacts = try self.container.viewContext.fetch(fetchRequest)
        // 데이터 출력
        for contact in contacts {
            print("Name: \(contact.name ?? "No Name"), Num: \(contact.num ?? "No Num"), Img: \(contact.img ?? "No Img")")
        }
    } catch {
        print("Failed to fetch data: \(error)")
    }
}

CoreData의 재설정 (Reset)

id 속성을 만들어놓기 전에 만든 엔티티들이 새로 수정한 delete 메서드로는 화면상에서 삭제가 안돼서ㅋㅋㅋㅋ (id로 삭제되게 수정했는데 기존 데이터들은 id가 없으니까...) delete 메서드를 다시 수정하기보단 초기화 메서드를 하나 만들어두는게 나을 것 같아서 만들었다.
영구 저장소를 삭제하고 다시 추가하는 방식이다.

// container 초기화 - 필요시 SceneDelegate에서 작동
func resetPersistentStore() {
    let storeCoordinator = container.persistentStoreCoordinator
    let storeURL = container.persistentStoreDescriptions.first?.url
    
    // 기존 저장소 삭제
    if let storeURL = storeURL {
        do {
            // 영구 저장소 삭제
            try storeCoordinator.destroyPersistentStore(at: storeURL, ofType: NSSQLiteStoreType, options: nil)
            print("Persistent store destroyed")
            
            // 다시 추가
            try storeCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
            print("Persistent store re-added")
        } catch {
            print("Failed to reset persistent store: \(error)")
        }
    }
}

사실 이런 메서드를 쓰는 것보다, 다른 곳에서 갖다붙이는게 제일 힘들었다.

  • TableView에서 파일에서 [PokemonContact] 속성으로 객체를 만들고 DataSource에서 불러온다던지...
  • DetailView에서 case를 나눠서 createData / updateData 하는 부분이라던지...
  • tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle 에서 deleteData 하는 부분이라던지...

하는 것들이다. 지금 다 해놔서 이렇게 말하기 쉬운거지 제일 시간이 오래 걸렸음. 그래도 해보니까 재밌다.

시간 될 때 UserDefaults로도 리팩토링 해봐야지...

profile
정체되지 않는 성장

0개의 댓글