DEV Community

Paul Allies
Paul Allies

Posted on • Updated on • Originally published at paulallies.Medium

CoreData and Swift

Continuing with our clean architecture, let's write a simple Todo CoreData Datasource in swift. Our applications repository method would use the a DataSource, in this case the TodoCoreDataSource. Here is a proposed files and their locations:

├── Core
└── Data
    ├── DataSource
        ├── TodoDataSource.swift
        └── CoreData
            ├── Main.xcdatamodeld
            └── TodoCoreDataSourceImpl.swift
    ├── Repository
        └── TodoRepositoryImpl.swift

├── Presentation
└── Domain
    ├── Model
    │   └── Todo.swift
    ├── Error
    │   └── TodoError.swift
    └── Repository
        └── TodoRepository.swift
Enter fullscreen mode Exit fullscreen mode

Let's first specify our Domain Model and Repository

Domain/Model/Todo.swift

struct Todo: Identifiable{
    let id: UUID
    let title: String
    let isCompleted: Bool
}

Enter fullscreen mode Exit fullscreen mode

Domain/Error/TodoError.swift

enum TodoError: Error{
    case DataSourceError, CreateError, DeleteError, UpdateError, FetchError
}
Enter fullscreen mode Exit fullscreen mode

Domain/Repository/TodoRepository.swift

protocol TodoRepository {
    func getTodos() async  -> Result<[Todo], TodoError>
    func getTodo(id: UUID) async  -> Result<Todo? , TodoError>
    func deleteTodo(_ id: UUID) async  -> Result<Bool, TodoError>
    func createTodo(_ todo: Todo) async  -> Result<Bool, TodoError>
    func updateTodo(_ todo: Todo) async  -> Result<Bool, TodoError>
}
Enter fullscreen mode Exit fullscreen mode

Data/DataSource/TodoDataSource.swift

protocol TodoDataSource{
    func getAll() async throws -> [Todo]
    func getById(_ id: UUID) async throws  -> Todo?
    func delete(_ id: UUID) async throws -> ()
    func create(todo: Todo) async throws -> ()
    func update(id: UUID, todo: Todo) async throws -> ()
}

Enter fullscreen mode Exit fullscreen mode

let's now create a CoreData Model by adding a new file and choosing Core Data -> Data Model and creating at TodoCoreDataEntity:

Domain/DataSource/CoreData/Main.xcdatamodeld

Core Data Model

To use the CoreData Main Model we need to create a data source that conforms to the TodoDataSource so that our TodoRepository can use it:

struct TodoCoreDataSourceImpl: TodoDataSource {
    let container: NSPersistentContainer

    init(){
        container = NSPersistentContainer(name: "Main")
        container.loadPersistentStores { description, error in
            if error != nil {
                fatalError("Cannot Load Core Data Model")
            }
        }
    }

    func getAll() throws -> [Todo]{
        let request = TodoCoreDataEntity.fetchRequest()
        return try container.viewContext.fetch(request).map({ todoCoreDataEntity in
            Todo(
                id: todoCoreDataEntity.id!,
                 title: todoCoreDataEntity.title!,
                isCompleted: todoCoreDataEntity.is_completed
            )
        })

    }

    func getById(_ id: UUID)  throws  -> Todo?{
        let todoCoreDataEntity = try getEntityById(id)!
        return Todo(
            id: todoCoreDataEntity.id!,
            title: todoCoreDataEntity.title!,
            isCompleted: todoCoreDataEntity.is_completed
        )

    }

    func delete(_ id: UUID) throws -> (){
        let todoCoreDataEntity = try getEntityById(id)!
        let context = container.viewContext;
        context.delete(todoCoreDataEntity)
        do{
            try context.save()
        }catch{
            context.rollback()
            fatalError("Error: \(error.localizedDescription)")
        }

    }

    func update(id: UUID, todo: Todo) throws -> (){
        let todoCoreDataEntity = try getEntityById(id)!
        todoCoreDataEntity.is_completed = todo.isCompleted
        todoCoreDataEntity.title = todo.title
        saveContext()
    }

    func create(todo: Todo) throws -> (){
        let todoCoreDataEntity = TodoCoreDataEntity(context: container.viewContext)
        todoCoreDataEntity.is_completed = todo.isCompleted
        todoCoreDataEntity.title = todo.title
        todoCoreDataEntity.id = todo.id
        saveContext()
    }


    private func getEntityById(_ id: UUID)  throws  -> TodoCoreDataEntity?{
        let request = TodoCoreDataEntity.fetchRequest()
        request.fetchLimit = 1
        request.predicate = NSPredicate(
            format: "id = %@", id.uuidString)
        let context =  container.viewContext
        let todoCoreDataEntity = try context.fetch(request)[0]
        return todoCoreDataEntity

    }

    private func saveContext(){
        let context = container.viewContext
        if context.hasChanges {
            do{
                try context.save()
            }catch{
                fatalError("Error: \(error.localizedDescription)")
            }
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

and then lastly our TodoRepositoryImpl would implement our TodoRepository and call our Datasource:

struct TodoRepositoryImpl: TodoRepository{

    var dataSource: TodoDataSource

    func getTodo(id: UUID) async  -> Result<Todo?, TodoError> {
        do{
            let _todo =  try await dataSource.getById(id)
            return .success(_todo)
        }catch{
            return .failure(.FetchError)
        }

    }

    func deleteTodo(_ id: UUID) async ->  Result<Bool, TodoError>  {
        do{
            try await dataSource.delete(id)
            return .success(true)
        }catch{
            return .failure(.DeleteError)
        }

    }

    func createTodo(_ todo: Todo) async ->  Result<Bool, TodoError>   {
        do{
            try await dataSource.create(todo: todo)
            return .success(true)
        }catch{
            return .failure(.CreateError)
        }

    }

    func updateTodo(_ todo: Todo) async ->  Result<Bool, TodoError>   {
        do{
            try await dataSource.update(id: todo.id, todo:todo)
            return .success(true)
        }catch{
            return .failure(.UpdateError)
        }

    }

    func getTodos() async -> Result<[Todo], TodoError> {
        do{
            let _todos =  try await dataSource.getAll()
            return .success(_todos)
        }catch{
            return .failure(.FetchError)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)