Skip to content

Commit

Permalink
Merge pull request #75 from boostcampwm-2024/feature/build-core-data
Browse files Browse the repository at this point in the history
CoreData 구축 및 테스트
  • Loading branch information
k2645 authored Nov 25, 2024
2 parents 3cc254a + e1a4c46 commit e3e1e01
Show file tree
Hide file tree
Showing 13 changed files with 546 additions and 2 deletions.
11 changes: 10 additions & 1 deletion MemorialHouse/MHCore/MHCore/MHError.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import Foundation

public enum MHError: Error, CustomStringConvertible {
public enum MHError: Error, CustomStringConvertible, Equatable {
case DIContainerResolveFailure(key: String)
case convertDTOFailure
case findEntityFailure
case saveContextFailure

public var description: String {
switch self {
case .DIContainerResolveFailure(let key):
"\(key)에 대한 dependency resolve 실패"
case .convertDTOFailure:
"Entity에 대한 DTO 변환 실패"
case .findEntityFailure:
"Entity 찾기 실패"
case .saveContextFailure:
"Update된 Context 저장 실패"
}
}
}
121 changes: 121 additions & 0 deletions MemorialHouse/MHData/MHData.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,36 @@
objects = {

/* Begin PBXBuildFile section */
A8BCE31D2CF38F6A00A9F32F /* MHData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE9AEB772CD7B46700F8471D /* MHData.framework */; platformFilter = ios; };
CE9AEB862CD7B49C00F8471D /* MHDomain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE9AEB852CD7B49C00F8471D /* MHDomain.framework */; };
CE9AEBF42CD7BA1800F8471D /* MHCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE9AEBF32CD7BA1800F8471D /* MHCore.framework */; };
DB382CC92CD9B24F000D7689 /* MHFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB382CC82CD9B24F000D7689 /* MHFoundation.framework */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
A8BCE31E2CF38F6A00A9F32F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = CE9AEB6E2CD7B46700F8471D /* Project object */;
proxyType = 1;
remoteGlobalIDString = CE9AEB762CD7B46700F8471D;
remoteInfo = MHData;
};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
A8BCE3192CF38F6A00A9F32F /* MHDataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MHDataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
CE9AEB772CD7B46700F8471D /* MHData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MHData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE9AEB852CD7B49C00F8471D /* MHDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MHDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE9AEBF32CD7BA1800F8471D /* MHCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MHCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB382CC82CD9B24F000D7689 /* MHFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MHFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
A8BCE31A2CF38F6A00A9F32F /* MHDataTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = MHDataTests;
sourceTree = "<group>";
};
A8C9336C2CDFBCDB007D932A /* MHData */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = MHData;
Expand All @@ -28,6 +45,14 @@
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
A8BCE3162CF38F6A00A9F32F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A8BCE31D2CF38F6A00A9F32F /* MHData.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
CE9AEB742CD7B46700F8471D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
Expand All @@ -45,6 +70,7 @@
isa = PBXGroup;
children = (
A8C9336C2CDFBCDB007D932A /* MHData */,
A8BCE31A2CF38F6A00A9F32F /* MHDataTests */,
CE9AEB842CD7B49C00F8471D /* Frameworks */,
CE9AEB782CD7B46700F8471D /* Products */,
);
Expand All @@ -54,6 +80,7 @@
isa = PBXGroup;
children = (
CE9AEB772CD7B46700F8471D /* MHData.framework */,
A8BCE3192CF38F6A00A9F32F /* MHDataTests.xctest */,
);
name = Products;
sourceTree = "<group>";
Expand Down Expand Up @@ -81,6 +108,29 @@
/* End PBXHeadersBuildPhase section */

/* Begin PBXNativeTarget section */
A8BCE3182CF38F6A00A9F32F /* MHDataTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = A8BCE3202CF38F6A00A9F32F /* Build configuration list for PBXNativeTarget "MHDataTests" */;
buildPhases = (
A8BCE3152CF38F6A00A9F32F /* Sources */,
A8BCE3162CF38F6A00A9F32F /* Frameworks */,
A8BCE3172CF38F6A00A9F32F /* Resources */,
);
buildRules = (
);
dependencies = (
A8BCE31F2CF38F6A00A9F32F /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
A8BCE31A2CF38F6A00A9F32F /* MHDataTests */,
);
name = MHDataTests;
packageProductDependencies = (
);
productName = MHDataTests;
productReference = A8BCE3192CF38F6A00A9F32F /* MHDataTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
CE9AEB762CD7B46700F8471D /* MHData */ = {
isa = PBXNativeTarget;
buildConfigurationList = CE9AEB7D2CD7B46700F8471D /* Build configuration list for PBXNativeTarget "MHData" */;
Expand Down Expand Up @@ -112,8 +162,12 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1610;
LastUpgradeCheck = 1610;
TargetAttributes = {
A8BCE3182CF38F6A00A9F32F = {
CreatedOnToolsVersion = 16.1;
};
CE9AEB762CD7B46700F8471D = {
CreatedOnToolsVersion = 16.1;
LastSwiftMigration = 1610;
Expand All @@ -138,11 +192,19 @@
projectRoot = "";
targets = (
CE9AEB762CD7B46700F8471D /* MHData */,
A8BCE3182CF38F6A00A9F32F /* MHDataTests */,
);
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
A8BCE3172CF38F6A00A9F32F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
CE9AEB752CD7B46700F8471D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -173,6 +235,13 @@
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
A8BCE3152CF38F6A00A9F32F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
CE9AEB732CD7B46700F8471D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
Expand All @@ -182,7 +251,50 @@
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
A8BCE31F2CF38F6A00A9F32F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
target = CE9AEB762CD7B46700F8471D /* MHData */;
targetProxy = A8BCE31E2CF38F6A00A9F32F /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
A8BCE3212CF38F6A00A9F32F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = B3PWYBKFUK;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.MHDataTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
A8BCE3222CF38F6A00A9F32F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = B3PWYBKFUK;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.MHDataTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
CE9AEB7E2CD7B46700F8471D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
Expand Down Expand Up @@ -392,6 +504,15 @@
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
A8BCE3202CF38F6A00A9F32F /* Build configuration list for PBXNativeTarget "MHDataTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A8BCE3212CF38F6A00A9F32F /* Debug */,
A8BCE3222CF38F6A00A9F32F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CE9AEB712CD7B46700F8471D /* Build configuration list for PBXProject "MHData" */ = {
isa = XCConfigurationList;
buildConfigurations = (
Expand Down
24 changes: 24 additions & 0 deletions MemorialHouse/MHData/MHData/DTO/BookCoverDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import MHFoundation
import MHDomain

public struct BookCoverDTO {
let identifier: UUID
let title: String
let imageURL: String?
let color: String
let category: String?
let favorite: Bool

func toBookCover() -> BookCover? {
guard let color = BookColor(rawValue: self.color) else { return nil }

return BookCover(
identifier: self.identifier,
title: self.title,
imageURL: self.imageURL,
color: color,
category: self.category,
favorite: self.favorite
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import MHFoundation
import MHCore

public protocol BookCoverStorage {
func create(data: BookCoverDTO) async -> Result<Void, MHError>
func fetch() async -> Result<[BookCoverDTO], MHError>
func update(with id: UUID, data: BookCoverDTO) async -> Result<Void, MHError>
func delete(with id: UUID) async -> Result<Void, MHError>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import MHFoundation
import MHCore
import CoreData

final class CoreDataBookCoverStorage {
private let coreDataStorage: CoreDataStorage

init(coreDataStorage: CoreDataStorage) {
self.coreDataStorage = coreDataStorage
}
}

extension CoreDataBookCoverStorage: BookCoverStorage {
func create(data: BookCoverDTO) async -> Result<Void, MHError> {
let context = coreDataStorage.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "BookCoverEntity", in: context) else {
return .failure(.DIContainerResolveFailure(key: "BookCoverEntity"))
}
let bookCover = NSManagedObject(entity: entity, insertInto: context)
bookCover.setValue(data.identifier, forKey: "identifier")
bookCover.setValue(data.title, forKey: "title")
bookCover.setValue(data.category, forKey: "category")
bookCover.setValue(data.color, forKey: "color")
bookCover.setValue(data.imageURL, forKey: "imageURL")
bookCover.setValue(data.favorite, forKey: "favorite")

await coreDataStorage.saveContext()
return .success(())
}

func fetch() async -> Result<[BookCoverDTO], MHError> {
let context = coreDataStorage.persistentContainer.viewContext
let request = BookCoverEntity.fetchRequest()

do {
let bookCoverEntities = try context.fetch(request)
let result = bookCoverEntities.compactMap { $0.toBookCoverDTO() }

return .success(result)
} catch {
MHLogger.debug("Error fetching book covers: \(error.localizedDescription)")
return .failure(.convertDTOFailure)
}
}

func update(with id: UUID, data: BookCoverDTO) async -> Result<Void, MHError> {
do {
let context = coreDataStorage.persistentContainer.viewContext
guard let newEntity = try getEntityByIdentifier(in: context, with: id) else {
return .failure(.findEntityFailure)
}
newEntity.setValue(data.identifier, forKey: "identifier")
newEntity.setValue(data.title, forKey: "title")
newEntity.setValue(data.category, forKey: "category")
newEntity.setValue(data.color, forKey: "color")
newEntity.setValue(data.imageURL, forKey: "imageURL")
newEntity.setValue(data.favorite, forKey: "favorite")

await coreDataStorage.saveContext()
return .success(())
} catch {
return .failure(.findEntityFailure)
}
}

func delete(with id: UUID) async -> Result<Void, MHError> {
do {
let context = coreDataStorage.persistentContainer.viewContext
guard let entity = try getEntityByIdentifier(in: context, with: id) else {
return .failure(.findEntityFailure)
}

context.delete(entity)

await coreDataStorage.saveContext()
return .success(())
} catch {
return .failure(.findEntityFailure)
}
}

private func getEntityByIdentifier(in context: NSManagedObjectContext, with id: UUID) throws -> BookCoverEntity? {
let request = BookCoverEntity.fetchRequest()

return try context.fetch(request).first(where: { $0.identifier == id })
}
}

// MARK: - BookCoverEntity Extension
extension BookCoverEntity {
func toBookCoverDTO() -> BookCoverDTO? {
guard let identifier = self.identifier,
let title = self.title,
let color = self.color else { return nil }

return BookCoverDTO(
identifier: identifier,
title: title,
imageURL: self.imageURL,
color: color,
category: self.category,
favorite: self.favorite
)
}
}
Loading

0 comments on commit e3e1e01

Please sign in to comment.