CoreData 활용한 생성, 삭제 구현

Eddy📱·2022년 7월 4일
0

Swift

목록 보기
7/11
post-thumbnail

코어데이터를 통해 영구적으로 데이터를 저장하고, 삭제하고, 가져오는 것을 실습으로 해보도록 한다.

메인 화면등록 화면등록된 화면

완성된 모습으로는 위와 같다.

데이터를 입력하고 저장하게 되면 이를 메인 테이블 뷰에서 보여주는 방식으로 하며, 이곳에서 삭제를 할 수 있는 구조다.

삭제하거나 데이터를 저장했을 때 보여주는 테이블뷰 셀에서는 실제 앱을 종료하더라도 그대로 유지되도록 해야한다.

이를 하기 위한 과정을 적어보도록 한다!

첫 번쨰로 코어데이터 파일을 만들어야한다.

앱 프로젝트를 시작할 때 Core Data를 선택할 수도 있고 그게 아니라면 파일을 추가할 수 있다.

그 이후 Entity, Attribute인 모델을 만들어주면 된다.
현재 사용하는 프로젝트에서는
body, category, Id 3가지가 필요하기에 3가지를 넣어준다.

Manual/None 옵션 선택한 이유는 아래와 같다.

access modifiers를 변경하고 추가로 편리한 메소드 또는 비즈니스 로직을 추가하기 위해 managed object subclass를 편집하기 위한 용도라고 한다.

이 옵션을 선택하면 Core Data는 class를 지원하기 위한 파일을 생성하지 않음. 

그리고 이곳에서 생성을 한다면 자동으로 2개의 파일을 생성해줄 것이다.

파일들은 모델 설정에 따라 만들어진 파일이다.

하나의 파일에서는 작성한 모델들이 쓰여 있으며 이에 대해 각각 유니크하다는 것을 보여준다.

또한 FetchRequest라는 메서드를 만들어서 이를 추후 데이터를 가져올 때 활용 할 수 있게 만든다.

import Foundation
import CoreData


extension Joke {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Joke> {
        return NSFetchRequest<Joke>(entityName: "Joke")
    }

    @NSManaged public var body: String?
    @NSManaged public var category: String?
    @NSManaged public var id: UUID?

}

extension Joke : Identifiable {

}

이제 초기 세팅은 끝!!

이제부터는 이에 코어데이터를 활용할 수 있는 container, context를 설정해주어야한다.

보통 AppDelegate에서 하지만, 이곳에서 하게되면 App단에서 하기 때문에 그 보다는 따로 파일을 구분해서 하는 것이 효율적이라고 판단하여 DataManager.swift 파일을 만들어서 진행하려고 한다.

일단 싱글톤 형식으로 만들어야 한다고 생각한다.

코어데이터에서 Data를 활용하는 클래스이기 때문에 이곳의 데이터는 모두 공유된 자원을 사용해야 한다고 판단하기에 싱글톤 형식으로 만들려고 한다.

(물론 정답은 아님!)

class DataManager {
    static let shared = DataManager()
    private init() {}

    var jokes = [Joke]()
}  

이처럼 선언을 해준 다음,

Core Data Stack이라는 것을 만들어야한다.

Core Data Stack이란 앱의 모델 layer를 관리하고 유지하는 역할을 한다.

그래서 이 큰 틀을 만든다음 내부에서 필요한 파일들을 만들어주어야 한다.

그래서 먼저 초기 설정을 해준다.

  private lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "DBJoke")
    container.loadPersistentStores { description, error in
        if let error = error {
        fatalError("Unable to load \(error)")
        }
    }
    return container
    }()

    var container: NSPersistentContainer {
    return persistentContainer
    }

데이터 모델로 만든 모델을 이용한 컨테이너를 만들도록 세팅해야한다!

그 다음에는 아래의 Core Data Stack에 있는 객체인 Context를 만들어준다.

  private var mainContext: NSManagedObjectContext {
    return persistentContainer.viewContext
  }

그리고 이제 내부에서 사용할 객체의 요소들을 위한 구조체를 만들 필요가 있다

import Foundation

struct JokeDTO {
    let id: UUID
    let content: String
    let category: Category
  
  enum Category: String {
        case 유행어
        case 아재개그
  }
}

여기에서는 이 정도가 필요하기 때문에 이를 선언하도록 해야한다.

물론 탭바 구현은 이곳에서 이루어지지 않지만 일단 작성해두도록 한다! (추후 해보시면 좋습니다!)

그 다음부터는 데이터를 가져오고, 저장하고, 삭제하는 코드를 만들 필요가 있다

  func saveContext(backgroundContext: NSManagedObjectContext? = nil) {
    let context = backgroundContext ?? mainContext
    guard context.hasChanges else { return }
    do {
      try context.save()
    } catch let error as NSError {
      print("Error: \(error), \(error.userInfo)")
    }
  }
  
  func fetchJokes() {
    let request = Joke.fetchRequest()
    
    do {
      self.jokes = try mainContext.fetch(request)
    } catch {
      print(error)
    }
  }

  func register(_ jokeDTO: JokeDTO) {
    let entity = NSEntityDescription.entity(forEntityName: "Joke", in: mainContext)
    if let entity = entity {
      let joke = NSManagedObject(entity: entity, insertInto: mainContext)
      joke.setValue(jokeDTO.id, forKey: "id")
      joke.setValue(jokeDTO.content, forKey: "body")
      joke.setValue(jokeDTO.category.rawValue, forKey: "category")
      
      self.saveContext()
    }
  }
  
  func delete(joke: Joke) {
    self.mainContext.delete(joke)
    self.saveContext()
  }

context를 저장하는 경우에는 데이터의 변화가 있을 경우 이를 저장하는 방식으로 이루어져있다.

그리고 fetch 경우에는 내부에 구현된 fetchRequest() 메서드를 활용해서 이를 호출한 다음 내부의 구조체에 옮겨 저장하는 식으로 한다.

데이터를 저장하는 경우에는 어떤 entity에 저장할 지를 먼저 정해주어야 하고, 이제 그 entity, context와 맞는 곳에 구조체를 만든 모델들을 이곳에서 넣어주면된다.

그리고 마지막에 Context에 저장해주면 저장이 완료된다.

데이터를 삭제하는 경우에는 mainContext에서 이를 제거해준다음 나머지 부분을 저장해주면 된다.

이제 동작하는 것들을 만들었으니 이를 ViewController에서 호출해야한다.

메인 VC에서는 아래와 같은 기능이 필요하다

  • data fetch
  • data update
  • data delete

이에 대한 코드는 아래와 같다

import UIKit

protocol DataDelegate: AnyObject {
    func update()
}

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    let dbManager = DataManager.shared
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dbManager.fetchJokes()
        self.tableView.dataSource = self
        self.navigationController?.navigationBar.prefersLargeTitles = true
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let destination = segue.destination as? RegisterViewController else { return }
        destination.delegate = self
    }
}

// MARK: - Delegate
extension ViewController: DataDelegate {
    func update() {
        self.dbManager.fetchJokes()
        self.tableView.reloadData()
    }
}

// MARK: - DataSource
extension ViewController: UITableViewDataSource {
    func tableView(
        _ tableView: UITableView,
        numberOfRowsInSection section: Int
    ) -> Int {
        return dbManager.jokes.count
    }
    
    func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(
        withIdentifier: JokeTableViewCell.identifier,
        for: indexPath) as? JokeTableViewCell
        else { return UITableViewCell() }
        
        cell.setup(joke: dbManager.jokes[indexPath.row])
        return cell
    }
    
    func tableView(
        _ tableView: UITableView,
        canEditRowAt indexPath: IndexPath
    ) -> Bool {
        return true
    }
    
    func tableView(
        _ tableView: UITableView,
        commit editingStyle: UITableViewCell.EditingStyle,
        forRowAt indexPath: IndexPath
    ) {
        if editingStyle == .delete {
        let joke = dbManager.jokes[indexPath.row]
        self.dbManager.delete(joke: joke)
        self.dbManager.jokes.remove(at: indexPath.row)
        self.tableView.reloadData()
        }
    }
}

그리고 두 번쨰 화면에서는 데이터를 저장하는 VC가 필요하다.

그러기 위한 두번째 뷰컨의 코드는 아래와 같다
이곳에서는 delegate를 통해 데이터 업데이트를 하도록 한다!

셀이 필요한데 그에 대한 코드는 아래와 같다

import UIKit

final class JokeTableViewCell: UITableViewCell {
  static var identifier: String {
    return String(describing: self)
  }
  
  @IBOutlet weak var jokeTitleLabel: UILabel!
  
  func setup(joke: Joke) {
    jokeTitleLabel.text = joke.body
  }
}
import UIKit

final class RegisterViewController: UIViewController {
    @IBOutlet weak var sentenceTextField: UITextField!
    @IBOutlet weak var segmentControl: UISegmentedControl!
    
    let dbManager = DataManager.shared
    weak var delegate: DataDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func registerButtonDidTap(_ sender: UIButton) {
        guard let content = sentenceTextField.text else { return }
        var category: JokeDTO.Category
        if segmentControl.selectedSegmentIndex == .zero {
        category = .유행어
        } else {
        category = .아재개그
        }
        
        let jokeDTO = JokeDTO(id: UUID(), content: content, category: category)
        dbManager.register(jokeDTO)
        
        self.delegate?.update()
        self.dismiss(animated: true)
  }
}

참고사이트
https://developer.apple.com/documentation/coredata
https://developer.apple.com/documentation/coredata/creating_a_core_data_model
https://zeddios.tistory.com/987

profile
Make a better world

0개의 댓글