DEV Community

Paul Allies
Paul Allies

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

4

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

Sentry blog image

The Visual Studio App Center’s retiring

But sadly….you’re not. See how to make the switch to Sentry for all your crash reporting needs.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay