CoreData and Swift
Tags: ios, programming, mobile, swift
Published: 2021-10-19 13:55:07

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
```

Let's first specify our Domain Model and Repository

#### Domain/Model/Todo.swift
```swift
struct Todo: Identifiable{
    let id: UUID
    let title: String
    let isCompleted: Bool
}

```

#### Domain/Error/TodoError.swift
```swift
enum TodoError: Error{
    case DataSourceError, CreateError, DeleteError, UpdateError, FetchError
}
```

#### Domain/Repository/TodoRepository.swift
```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>
}
```

#### Data/DataSource/TodoDataSource.swift
```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 -> ()
}

```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyxpty00q8dx6ugvqb6u.png)
 

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:

```swift
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)")
            }
        }
    }
    
}
```

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

```swift
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)
        }
    }
}
```