본문 바로가기

Swift

파일 매니저와 코어 데이터를 사용하여 텍스트와 이미지 저장하기

https://explorer89.tistory.com/245

 

Feed의 Id는 언제 생기나?

https://explorer89.tistory.com/244 데이터 모델 형식이 따른 장, 단점 (Feed VS [Feed])1. 기존 방식과 제안 방식 비교기존 방식:struct Feed { let id: UUID let title: String? let contents: String? let date: Date? let imagePath: [Stri

explorer89.tistory.com

 

구조체 정의

import Foundation
import UIKit

struct Feed: Codable {
    let title: String?
    let contents: String?
    let date: Date?
    let imagePath: [String]
}

struct FeedManager: Codable {
    let id: UUID
    let feed: Feed
    
    /// 새로운 피드를 생성할 때 사용하는 생성자
    init(feed: Feed) {
        self.id = UUID() // 새로운 UUID 생성
        self.feed = feed
    }
    
    /// Core Data에서 불러온 피드를 초기화할 때 사용하는 생성자
    init(id: UUID, feed: Feed) {
        self.id = id // 기존 ID 유지
        self.feed = feed
    }
}

 

1. Feed 구조체

Feed는 피드의 실제 데이터를 저장하는 역할을 합니다. 각 피드에 대한 기본적인 정보를 담고 있으며, UI에서 보여질 데이터와 저장할 데이터를 모두 포함합니다.

프로퍼티

struct Feed: Codable {
    let title: String?       // 피드의 제목
    let contents: String?    // 피드의 내용
    let date: Date?          // 피드 작성 날짜
    let imagePath: [String]  // 피드와 연관된 이미지 파일의 경로 배열 (파일 매니저에서 관리)
}

 

  • title: 피드의 제목(옵셔널).
  • contents: 피드 내용(옵셔널).
  • date: 피드가 작성된 날짜.
  • imagePath: 이미지 경로를 나타내는 문자열 배열. 각 문자열은 파일 매니저를 통해 저장된 이미지의 상대 경로를 나타냄.

Codable

  • Feed 구조체는 Codable 프로토콜을 채택하여 JSON과 같은 포맷으로 쉽게 인코딩 및 디코딩할 수 있습니다. 예를 들어 네트워크 통신 시 데이터를 직렬화하거나 디스크에 저장하기 용이합니다.

 

2. FeedManager 구조체

FeedManager는 각각의 피드에 고유한 ID(UUID)를 부여하고, 해당 ID와 연관된 Feed 데이터를 관리하는 역할을 합니다.

프로퍼티

struct FeedManager: Codable {
    let id: UUID         // 각 피드의 고유 ID
    let feed: Feed       // 해당 피드의 데이터
}

 

  • id: UUID 형식으로 된 고유 ID. 각 피드가 고유하게 식별될 수 있도록 관리합니다.
    • 새로운 피드를 작성할 때 자동으로 생성.
    • Core Data에서 불러온 데이터의 경우 기존 ID 유지.
  • feed: 위에서 정의한 Feed 구조체를 포함. Feed의 데이터를 캡슐화하여 관리합니다.

 

생성자

FeedManager는 두 가지 생성자를 제공합니다.

 

1. 새로운 피드를 생성할 때 사용하는 생성자

init(feed: Feed) {
    self.id = UUID() // 새로운 UUID 생성
    self.feed = feed
}
  • 새로운 피드를 생성할 때 UUID를 자동 생성하여 고유한 ID를 부여.
  • 사용 사례: 사용자가 새롭게 피드를 작성할 때.

 

2. Core Data에서 불러온 피드를 초기화할 때 사용하는 생성자

init(id: UUID, feed: Feed) {
    self.id = id // 기존 ID 유지
    self.feed = feed
}
  • Core Data 등 외부에서 데이터를 불러올 때 기존 ID를 유지하도록 설계.
  • 사용 사례: 저장된 데이터를 다시 불러와서 관리할 때.

 

3. Feed와 FeedManager의 관계

  • FeedManager는 Feed를 포함하여 고유 ID로 피드를 식별할 수 있도록 돕습니다.
  • 예를 들어, 동일한 피드를 수정하거나 삭제할 때 id를 사용해 특정 피드를 정확히 찾아낼 수 있습니다.
  • FeedManager는 Feed 데이터를 포함함으로써 데이터를 구조화하고 관리하는 데 유용합니다.

 

데이터 모델 사용 사례

  • 새로운 피드 생성
let newFeed = Feed(title: "오늘 하루", contents: "정말 즐거운 하루였습니다.", date: Date(), imagePath: [])
let feedManager = FeedManager(feed: newFeed)
print(feedManager.id) // 새로운 UUID 출력
  • Core Data에서 불러오기
let savedFeed = Feed(title: "저장된 피드", contents: "저장된 내용", date: Date(), imagePath: ["path1", "path2"])
let existingFeedManager = FeedManager(id: UUID(uuidString: "EXISTING_UUID")!, feed: savedFeed)
print(existingFeedManager.id) // 기존 ID 유지
  • FeedManager와 Feed를 활용한 식별
    • FeedManager.id를 통해 피드를 고유하게 식별.
    • FeedManager.feed를 통해 피드 데이터를 활용하여 UI에 표시하거나 데이터를 저장.

장점

  • 캡슐화: FeedManager를 통해 Feed와 관련된 데이터를 관리.
  • 유연성: 새로운 피드 생성 및 기존 데이터 관리 시 요구사항에 따라 다른 생성자 사용.
  • 고유성 보장: UUID를 활용하여 각 피드를 식별.
  • 데이터 직렬화 가능: Codable을 통해 JSON 직렬화 및 디코딩에 용이.

이 데이터 모델은 새로운 피드 작성, 기존 피드 수정, 데이터 저장 및 불러오기 등 다양한 기능을 유연하게 처리할 수 있는 구조를 제공합니다.

 

 

파일매니저 관리 

import Foundation
import UIKit


class FeedStorageManager {
    
    let fileManager = FileManager.default
    
    /// Documents 폴더 경로 가져오기
    private func getDocumentsDirectory() -> URL {
        fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
    }
    
    /// 이미지를 저장하고 경로를 반환하는 함수
    func saveImages(images: [UIImage], feedID: String) -> [String] {
        let feedFolder = getDocumentsDirectory().appendingPathComponent(feedID)
        
        // 폴더가 없다면? 생성
        if !FileManager.default.fileExists(atPath: feedFolder.path) {
            try? FileManager.default.createDirectory(at: feedFolder,
                                                     withIntermediateDirectories: true,
                                                     attributes: nil)
        }
        
        var savedImagesPaths: [String] = []
        
        for (index, image) in images.enumerated() {
            let fileName = "image_\(index).jpg"
            let fileURL = feedFolder.appendingPathComponent(fileName)
            
            if let imageData = image.jpegData(compressionQuality: 1.0) {
                try? imageData.write(to: fileURL)
                savedImagesPaths.append("\(feedID)/\(fileName)")    // 상대 경로 저장
            }
        }
        return savedImagesPaths
    }
    
    /// 저장한 이미지를 상대경로를 통해 불러오는 함수
    func loadImages(from relativePaths: [String]) -> [UIImage] {
        var images: [UIImage] = []
        
        for relativePath in relativePaths {
            let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
            
            if let image = UIImage(contentsOfFile: fullPath.path) {
                images.append(image)
            }
        }
        print("load image: \(images)")
        return images
    }

    /// 저장한 이미지를 삭제하는 함수
    func deleteImages(from relativePaths: [String]) {
        for relativePath in relativePaths {
            let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
            
            do {
                try fileManager.removeItem(at: fullPath)
                print("Deleted image at: \(fullPath.path)")
            } catch {
                print("Failed to delete image at: \(fullPath.path). Error: \(error)")
            }
        }
    }
}

 

 위 코드는 파일 매니저(FileManager)를 활용하여 이미지를 디스크에 저장하거나 불러오고 삭제하는 기능을 제공하는 FeedStorageManager 클래스입니다. 이 클래스는 iOS의 Documents 폴더를 사용하여 파일을 관리하며, 앱 데이터를 로컬 저장소에 보관하는 일반적인 방법을 구현하고 있습니다.

 

주요 역할

  1. 이미지 저장 (saveImages)
    • 사용자로부터 제공된 여러 이미지를 앱의 Documents 디렉토리에 저장.
    • 저장된 파일의 상대 경로를 반환.
  2. 이미지 불러오기 (loadImages)
    • 저장된 이미지의 상대 경로를 받아 이미지를 로드.
    • UIImage 객체 배열로 반환.
  3. 이미지 삭제 (deleteImages)
    • 저장된 이미지의 상대 경로를 받아 파일을 삭제.

 

 

메서드 상세 설명

1. Documents 폴더 경로 가져오기 (getDocumentsDirectory)

private func getDocumentsDirectory() -> URL {
    fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
}
  • 역할: 앱의 Documents 폴더 URL 경로를 반환.
  • 사용 이유:
    • iOS 앱에서 데이터를 영구적으로 저장하려면 Documents 폴더를 사용하는 것이 일반적.
    • 사용자 데이터는 여기 저장되어야 iCloud 백업 및 복원이 가능.

 

2. 이미지 저장 (saveImages)

func saveImages(images: [UIImage], feedID: String) -> [String]
  • 역할: UIImage 객체 배열을 특정 피드에 대한 디렉토리에 저장.
  • 매개변수:
    • images: 저장할 이미지 배열.
    • feedID: 각 피드마다 고유한 디렉토리를 생성하기 위한 ID.
  • 과정:
    1. feedID를 기반으로 한 폴더를 생성.
    2. 이미지를 jpegData로 변환하여 .jpg 형식으로 저장.
    3. 저장된 파일의 상대 경로를 배열로 반환.
  • 리턴값:
    • 파일들의 상대 경로 배열 (e.g., ["feedID/image_0.jpg", "feedID/image_1.jpg"]).

 

3. 이미지 불러오기 (loadImages)

func loadImages(from relativePaths: [String]) -> [UIImage]
  • 역할: 상대 경로를 받아 이미지를 로드하여 반환.
  • 매개변수:
    • relativePaths: 저장된 이미지의 상대 경로 배열.
  • 과정:
    1. 각 상대 경로를 기반으로 절대 경로를 생성.
    2. 해당 경로에서 이미지를 로드하여 UIImage 객체로 변환.
    3. 이미지 배열을 반환.
  • 리턴값:
    • UIImage 객체 배열.

 

4. 이미지 삭제 (deleteImages)

func deleteImages(from relativePaths: [String])
  • 역할: 상대 경로를 받아 해당 파일들을 삭제.
  • 매개변수:
    • relativePaths: 삭제할 이미지의 상대 경로 배열.
  • 과정:
    1. 각 상대 경로를 절대 경로로 변환.
    2. FileManager의 removeItem(at:) 메서드를 사용해 파일 삭제.
    3. 삭제 성공 여부를 로그에 출력.
  • 예외 처리:
    • 파일이 존재하지 않거나 삭제할 수 없는 경우 에러를 출력.

 

장점

  1. 캡슐화:
    • 모든 파일 관리 작업이 FeedStorageManager 내에 포함되어 코드가 깔끔하고 재사용 가능.
    • 다른 클래스에서는 이미지 저장/불러오기/삭제 로직을 신경 쓰지 않아도 됨.
  2. 상대 경로 사용:
    • 데이터베이스(예: Core Data)에는 상대 경로만 저장.
    • 앱의 Documents 디렉토리 구조를 변경해도 데이터베이스 구조를 변경하지 않아도 됨.
  3. 확장성:
    • 다양한 저장 포맷(예: PNG, GIF)을 추가하거나 압축 품질을 조정할 수 있음.
  4. iOS 권장 방식:
    • 앱에서 데이터를 파일로 저장하거나 불러올 때 Documents 디렉토리를 사용하는 것은 iOS에서 권장하는 방식.

 

 

코어 데이터 관리

import Foundation
import UIKit
import CoreData


class FeedCoreDataManager {
    
    static let shared = FeedCoreDataManager()
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    private let feedStorageManager = FeedStorageManager()

    /// 코어 데이터에 사용자가 작성한 내용 저장하는 메서드
    func saveFeedItem(feedItem: FeedManager) {
        let feedModel = FeedModel(context: context)
        
        // Feed의 데이터 설정
        feedModel.id = feedItem.id
        feedModel.title = feedItem.feed.title
        feedModel.date = feedItem.feed.date
        feedModel.content = feedItem.feed.contents
        
        // 여러 이미지 경로를 문자열로 저장 (쉼표로 구분)
        feedModel.imagePath = feedItem.feed.imagePath.isEmpty ? nil : feedItem.feed.imagePath.joined(separator: ",")
        
        // Core Data 저장
        do {
            try context.save()
            print("Feed item saved successfully.")
        } catch {
            print("Failed to save feed item: \(error)")
        }
        
    }
    
    /// feed 데이터를 불러오는 함수
    func loadFeedItem() -> [FeedManager] {
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
        
        var feedItems: [FeedManager] = []
        
        do {
            let feedModels = try context.fetch(request)
            
            for entity in feedModels {
                guard let entityID = entity.id else {
                    print("Warning: Entity ID is missing. Skipping this feed.")
                    continue
                }
                
                // imagePath 문자열을 배열로 변환
                let imagePaths: [String] = entity.imagePath?.components(separatedBy: ",") ?? []
                
                // FeedManager 생성
                let feedItem = FeedManager(
                    id: entityID, // Core Data에서 저장한 ID 사용
                    feed: Feed(
                        title: entity.title,
                        contents: entity.content,
                        date: entity.date,
                        imagePath: imagePaths
                    )
                )

                feedItems.append(feedItem)
            }
        } catch {
            print("Failed to load feed items: \(error)")
        }
        
        return feedItems
    }
    
    
    ///  feed를 삭제하면서 동시에 filemnager에 저장된 이미지도 삭제하는 함수
    func deleteFeedItems(feedItem: FeedManager) {
        
        // 코어 데이터 불러오기
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        let predicate = NSPredicate(format: "id == %@", feedItem.id as CVarArg)
        request.predicate = predicate
        
        do {
            let fetchResults = try context.fetch(request)
            if let feedToDelete = fetchResults.first {
                
                // 이미지 경로 삭제
                let imagePaths = feedToDelete.imagePath?.components(separatedBy: ",") ?? []
                FeedStorageManager().deleteImages(from: imagePaths)
                
                // 코어 데이터에서 삭제
                context.delete(feedToDelete)
                try context.save()
                print("Feed item and images deleted succeessfully")
            } else {
                print("Feed item not found in Core Data.")
            }
        } catch {
            print("Failed to delete feed item: \(error)")
        }
    }
    
    
    /// 사용자가 피드를 수정한 경우
    func updateFeedItem(feedID: UUID, updatedFeed: Feed, newImages: [UIImage]) {
        
        // 기존 데이터 불러오기
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        let predicate = NSPredicate(format: "id == %@", feedID as CVarArg)
        request.predicate = predicate
        
        do {
            let fetchResults = try context.fetch(request)
            
            if let feedToUpdate = fetchResults.first {
                
                // 불러온 데이터에 새로 작성한 데이터 넣기
                
                feedToUpdate.title = updatedFeed.title
                feedToUpdate.content = updatedFeed.contents
                feedToUpdate.date = feedToUpdate.date
                
                // 기존 이미지 경로 삭제 및 새 이미지 저장
                let oldImagePaths = feedToUpdate.imagePath?.components(separatedBy: ",") ?? []
                FeedStorageManager().deleteImages(from: oldImagePaths)
                
                let newImagePaths = FeedStorageManager().saveImages(images: newImages, feedID: feedID.uuidString)
                feedToUpdate.imagePath = newImagePaths.joined(separator: ",")
                
                // 변경된 데이터 저장
                try context.save()
                print("Feed item updated successfully")
            } else {
                print("Feed item not found")
            }
        } catch {
            print("Failed to update feed item: \(error)")
        }
    }
}

 

 FeedCoreDataManager는 Core Data를 사용해 피드 데이터를 관리하는 클래스입니다.

피드 데이터에는 텍스트 데이터(제목, 내용, 날짜)와 이미지 데이터(상대 경로로 저장됨)가 포함되어 있습니다.

이미지는 파일매니저를 통해 저장되고, Core Data에는 이미지를 가리키는 상대 경로만 저장됩니다.

 

주요 기능과 동작 원리

1. FeedCoreDataManager의 구조

  • shared 싱글톤: 앱 어디서나 동일한 인스턴스를 사용하도록 싱글톤 패턴을 적용.
  • context: Core Data의 NSManagedObjectContext를 통해 데이터를 저장하거나 불러옵니다.
  • feedStorageManager: 파일매니저를 사용해 이미지를 저장, 삭제, 로드하는 기능을 지원합니다.
class FeedCoreDataManager {
    
    static let shared = FeedCoreDataManager()
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    private let feedStorageManager = FeedStorageManager()
    ...

 

static let shared

static let shared = FeedCoreDataManager()
  • 싱글톤 패턴을 구현한 부분입니다.
  • FeedCoreDataManager는 앱 내에서 하나의 인스턴스만 생성되도록 설계되었습니다.
  • 언제나 동일한 shared 인스턴스를 사용하므로 메모리를 절약하고, 데이터 관리의 일관성을 유지할 수 있습니다.
  • FeedCoreDataManager.shared를 호출하면 해당 클래스의 동일한 인스턴스를 반환.
  • 이 방식은 Core Data를 통해 데이터를 저장, 삭제, 조회할 때 매번 새 인스턴스를 생성하지 않고, 동일한 객체를 사용해 관리하도록 보장합니다.

 

private let context

private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
  • Core Data의 NSManagedObjectContext 객체입니다.
  • Core Data에서 데이터를 저장하거나 불러오고 삭제하는 모든 작업은 context를 통해 이루어집니다.
  • 구성 요소:
    1. UIApplication.shared.delegate:
      • 앱의 AppDelegate에 접근.
    2. persistentContainer:
      • Core Data의 저장소와 연관된 객체. 데이터베이스와 관련된 작업을 처리.
    3. viewContext:
      • Core Data 작업의 기본 NSManagedObjectContext. 데이터를 읽거나 쓰는 데 사용됩니다.
  • 이유:
    • context를 직접 초기화하지 않고 앱 전체에서 관리되는 persistentContainer.viewContext를 가져와 데이터의 일관성을 유지.

 

private let feedStorageManager

private let feedStorageManager = FeedStorageManager()
  • 파일 매니저를 사용해 이미지 데이터를 관리하기 위해 사용하는 객체입니다.
  • FeedStorageManager 클래스의 인스턴스를 생성하고, 이를 통해 이미지 데이터를 저장, 불러오기, 삭제합니다.
  • 목적:
    • FeedCoreDataManager에서 파일 매니저를 직접 호출하는 대신, FeedStorageManager를 통해 파일 시스템 작업을 캡슐화하여 관리.

 

saveFeedItem() 메서드

/// 코어 데이터에 사용자가 작성한 내용 저장하는 메서드
    func saveFeedItem(feedItem: FeedManager) {
        let feedModel = FeedModel(context: context)
        
        // Feed의 데이터 설정
        feedModel.id = feedItem.id
        feedModel.title = feedItem.feed.title
        feedModel.date = feedItem.feed.date
        feedModel.content = feedItem.feed.contents
        
        // 여러 이미지 경로를 문자열로 저장 (쉼표로 구분)
        feedModel.imagePath = feedItem.feed.imagePath.isEmpty ? nil : feedItem.feed.imagePath.joined(separator: ",")
        
        // Core Data 저장
        do {
            try context.save()
            print("Feed item saved successfully.")
        } catch {
            print("Failed to save feed item: \(error)")
        }
        
    }

 

saveFeedItem(feedItem:) 메서드로, Core Data에 사용자가 작성한 피드 데이터를 저장하는 역할을 합니다. 사용자가 작성한 텍스트(제목, 내용)와 이미지 경로 데이터를 Core Data의 FeedModel 객체에 매핑한 후 저장하는 로직이 구현되어 있습니다.

 

  • let feedModel = FeedModel(context: context)
    • Core Data의 엔티티인 FeedModel의 인스턴스를 생성합니다.
    • 생성 시 context를 사용하여 Core Data의 관리 객체로 초기화합니다.
    • context는 Core Data와 데이터 저장소 간의 인터페이스 역할을 합니다.
  • 데이터 매핑
    • 사용자가 작성한 데이터를 FeedModel에 할당합니다.
    • 각의 프로퍼티가 FeedManager 객체에서 전달된 데이터를 기반으로 설정됩니다.
feedModel.id = feedItem.id
feedModel.title = feedItem.feed.title
feedModel.date = feedItem.feed.date
feedModel.content = feedItem.feed.contents

 

  • 이미지 경로 저장
    • Core Data는 대용량 데이터를 저장하는 데 적합하지 않으므로, 이미지 자체는 파일 매니저로 저장하고, 이미지 경로만 Core Data에 저장합니다.
    • 이미지 경로(imagePath)는 문자열 배열([String])로 관리되며, 이를 쉼표(,)로 구분된 하나의 문자열로 변환해 저장합니다.
    • 예를 들어, 배열이 ["path1", "path2", "path3"]라면, 결과는 "path1,path2,path3"이 됩니다.
feedModel.imagePath = feedItem.feed.imagePath.isEmpty ? nil : feedItem.feed.imagePath.joined(separator: ",")

 

🔥 왜 쉼표로 문자열을 저장하는가?

  1. Core Data는 배열 데이터를 기본적으로 지원하지 않습니다.
    • 따라서 배열 데이터를 저장하려면 JSON이나 쉼표로 연결된 문자열 형식으로 변환해야 합니다.
  2. 쉼표로 연결된 문자열은 처리하기 쉽습니다.
    • 데이터를 저장할 때는 joined(separator: ",")를 사용하고,
    • 불러올 때는 components(separatedBy: ",")를 사용해 다시 배열로 변환할 수 있습니다.

 

  • Core Data 저장
    • context.save()를 호출하여 feedModel에 설정된 데이터를 저장소에 영구적으로 저장합니다.
    • 성공 시 "Feed item saved successfully."를 출력하고, 실패 시 에러를 출력합니다.

 

loadFeedItem() 메서드

/// feed 데이터를 불러오는 함수
    func loadFeedItem() -> [FeedManager] {
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
        
        var feedItems: [FeedManager] = []
        
        do {
            let feedModels = try context.fetch(request)
            
            for entity in feedModels {
                guard let entityID = entity.id else {
                    print("Warning: Entity ID is missing. Skipping this feed.")
                    continue
                }
                
                // imagePath 문자열을 배열로 변환
                let imagePaths: [String] = entity.imagePath?.components(separatedBy: ",") ?? []
                
                // FeedManager 생성
                let feedItem = FeedManager(
                    id: entityID, // Core Data에서 저장한 ID 사용
                    feed: Feed(
                        title: entity.title,
                        contents: entity.content,
                        date: entity.date,
                        imagePath: imagePaths
                    )
                )

                feedItems.append(feedItem)
            }
        } catch {
            print("Failed to load feed items: \(error)")
        }
        
        return feedItems
    }

 

위의 코드는 Core Data에 저장된 FeedModel 데이터를 불러와서 FeedManager 타입으로 변환한 뒤 반환하는 함수입니다.

핵심 요약

  1. Core Data에서 데이터를 가져온다.
  2. ID가 있는 데이터만 처리한다.
  3. 이미지 경로 문자열을 배열로 변환한다.
  4. FeedManager 객체로 매핑한다.
  5. FeedManager 배열로 반환한다.

 

Core Data 요청 생성

let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
  • NSFetchRequest: Core Data에서 데이터를 가져오기 위한 요청 객체를 생성합니다.
  • FeedModel.fetchRequest(): FeedModel 엔티티에 해당하는 데이터를 요청합니다.
  • sortDescriptors: 불러온 데이터를 정렬할 조건을 설정합니다.
    • 여기서는 date 속성을 기준으로 내림차순(ascending: false)으로 정렬합니다.

초기화

var feedItems: [FeedManager] = []
  • Core Data에서 불러온 데이터를 변환하여 저장할 배열(feedItems)을 초기화합니다.

 

Core Data 요청 실행

let feedModels = try context.fetch(request)
  • Core Data에서 데이터를 가져옵니다.
  • 성공하면 feedModels에 데이터가 저장됩니다.
  • 실패하면 catch 블록으로 이동하여 에러를 출력합니다.

 

데이터 변환 및 매핑

for entity in feedModels {
    guard let entityID = entity.id else {
        print("Warning: Entity ID is missing. Skipping this feed.")
        continue
    }
  • Core Data에서 가져온 각 FeedModel 엔티티(entity)를 순회합니다.
  • guard let entityID = entity.id:
    • FeedModel의 id 값이 존재하는지 확인합니다.
    • ID가 없으면 해당 데이터를 건너뜁니다(continue).

 

이미지 경로 처리

let imagePaths: [String] = entity.imagePath?.components(separatedBy: ",") ?? []
  • Core Data에 저장된 이미지 경로 문자열(imagePath)을 쉼표(,)를 기준으로 분리하여 배열로 변환합니다.
  • 예:
    • 저장된 문자열: "image1.jpg,image2.jpg,image3.jpg"
    • 변환된 배열: ["image1.jpg", "image2.jpg", "image3.jpg"]

 

FeedManager 생성

feedItems.append(feedItem)
  • 변환된 FeedManager 객체를 feedItems 배열에 추가합니다.

 

deleteFeedItems() 메서드

func deleteFeedItems(feedItem: FeedManager) {
        
        // 코어 데이터 불러오기
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        let predicate = NSPredicate(format: "id == %@", feedItem.id as CVarArg)
        request.predicate = predicate
        
        do {
            let fetchResults = try context.fetch(request)
            if let feedToDelete = fetchResults.first {
                
                // 이미지 경로 삭제
                let imagePaths = feedToDelete.imagePath?.components(separatedBy: ",") ?? []
                FeedStorageManager().deleteImages(from: imagePaths)
                
                // 코어 데이터에서 삭제
                context.delete(feedToDelete)
                try context.save()
                print("Feed item and images deleted succeessfully")
            } else {
                print("Feed item not found in Core Data.")
            }
        } catch {
            print("Failed to delete feed item: \(error)")
        }
    }

 

전체 작업 순서

  1. Core Data에서 요청 생성:
    • id를 기준으로 데이터를 찾는 조건을 설정합니다.
  2. 데이터 검색:
    • id가 일치하는 데이터를 Core Data에서 검색합니다.
  3. 이미지 파일 삭제:
    • Core Data에서 가져온 이미지 경로를 기반으로 파일 매니저에서 이미지를 삭제합니다.
  4. Core Data 데이터 삭제:
    • 해당 데이터를 Core Data에서 삭제합니다.
  5. 변경 사항 저장:
    • Core Data에서의 삭제 작업을 영구적으로 저장합니다.

 

Core Data에서 특정 데이터를 찾기 위한 요청 생성

let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
let predicate = NSPredicate(format: "id == %@", feedItem.id as CVarArg)
request.predicate = predicate
  • NSFetchRequest<FeedModel>: FeedModel 엔티티에 대한 요청 객체를 생성합니다.
  • NSPredicate: 조건을 설정하여 원하는 데이터를 필터링합니다.
    • 여기서는 id 값이 feedItem.id와 일치하는 데이터를 찾습니다.
    • feedItem.id as CVarArg:
      • Core Data 쿼리에서 UUID 타입의 값을 사용하려면 CVarArg로 변환이 필요합니다.
  • request.predicate: 요청에 조건을 설정합니다

 

Core Data에서 데이터 검색

let fetchResults = try context.fetch(request)
  • Core Data에서 요청을 실행합니다.
  • 조건에 맞는 데이터를 검색하여 결과를 반환합니다.
  • 결과는 배열(fetchResults)로 반환됩니다.

 

이미지 경로 삭제

let imagePaths = feedToDelete.imagePath?.components(separatedBy: ",") ?? []
FeedStorageManager().deleteImages(from: imagePaths)
  • 이미지 경로 추출:
    • Core Data에 저장된 imagePath 문자열을 쉼표(,)로 분리하여 배열로 변환합니다.
  • 이미지 파일 삭제:
    • FeedStorageManager의 deleteImages 메서드를 사용해 해당 경로의 이미지를 삭제합니다.
    • 예:
      • 저장된 문자열: "feedID/image_0.jpg,feedID/image_1.jpg"
      • 변환된 배열: ["feedID/image_0.jpg", "feedID/image_1.jpg"]

 

Core Data에서 데이터 삭제

context.delete(feedToDelete)
try context.save()
  • context.delete(feedToDelete):
    • Core Data에서 해당 객체를 삭제합니다.
  • try context.save():
    • 변경 사항을 저장하여 데이터 삭제를 영구적으로 반영합니다.

 

 

updateFeedItem(feedID: UUID, updatedFeed: Feed, newImages: [UIImage]

/// 사용자가 피드를 수정한 경우
    func updateFeedItem(feedID: UUID, updatedFeed: Feed, newImages: [UIImage]) {
        
        // 기존 데이터 불러오기
        let request: NSFetchRequest<FeedModel> = FeedModel.fetchRequest()
        let predicate = NSPredicate(format: "id == %@", feedID as CVarArg)
        request.predicate = predicate
        
        do {
            let fetchResults = try context.fetch(request)
            
            if let feedToUpdate = fetchResults.first {
                
                // 불러온 데이터에 새로 작성한 데이터 넣기
                
                feedToUpdate.title = updatedFeed.title
                feedToUpdate.content = updatedFeed.contents
                feedToUpdate.date = feedToUpdate.date
                
                // 기존 이미지 경로 삭제 및 새 이미지 저장
                let oldImagePaths = feedToUpdate.imagePath?.components(separatedBy: ",") ?? []
                FeedStorageManager().deleteImages(from: oldImagePaths)
                
                let newImagePaths = FeedStorageManager().saveImages(images: newImages, feedID: feedID.uuidString)
                feedToUpdate.imagePath = newImagePaths.joined(separator: ",")
                
                // 변경된 데이터 저장
                try context.save()
                print("Feed item updated successfully")
            } else {
                print("Feed item not found")
            }
        } catch {
            print("Failed to update feed item: \(error)")
        }
    }

 

사용자가 피드를 수정할 때 기존 데이터를 업데이트하고, 관련된 이미지 파일도 새롭게 저장 및 관리하는 역할을 합니다.

 

전체 작업 순서

  1. Core Data에서 id를 기준으로 기존 데이터를 검색합니다.
  2. 검색된 데이터를 확인하고 업데이트를 진행합니다.
  3. 텍스트 데이터(title, content, date)를 업데이트합니다.
  4. 기존 이미지 파일을 삭제하고 새 이미지를 저장합니다.
  5. Core Data에서 이미지 경로를 새로 업데이트합니다.
  6. 변경 사항을 저장하여 업데이트 작업을 완료합니다.

 

핵심 요약

  • 데이터 업데이트와 파일 관리 결합:
    • 텍스트 데이터와 이미지 데이터를 함께 관리하며 업데이트합니다.
  • 기존 데이터 정리:
    • 기존 파일 및 데이터를 삭제하여 중복 데이터가 생기지 않도록 합니다.
  • 유지보수 용이성:
    • Core Data와 파일 매니저를 통합적으로 관리해 수정 및 데이터 관리가 간단합니다.

'Swift' 카테고리의 다른 글

async과 await 개요  (0) 2024.12.10
컨텍스트 (context)는 무엇인가요?  (1) 2024.12.10
Feed의 Id는 언제 생기나?  (0) 2024.11.26
데이터 모델 형식이 따른 장, 단점 (Feed VS [Feed])  (0) 2024.11.26
ArraySlice와 Array  (0) 2024.11.25