Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - implementing repository pattern with generics

I have been doing some research into repository pattern as want to use it new project I am working on. However I am having issues getting it working with generics

I have been following this guide here as an example

https://medium.com/@frederikjacques/repository-design-pattern-in-swift-952061485aa

Which does a fairly good job of explaining it. However, the guide leaves off one important detail.. which is using dependancy injection with generics.

In example code, he shows this

class ArticleFeedViewModel {
  let articleRepo:ArticleRepository
  init( articleRepo:ArticleRepository = WebArticleRepository() ) {

    self.articleRepo = articleRepo
  }
}

Which works fine if you are not using generics. But once you change ArticleRepository to Repository example... so from

protocol ArticleRepository {
    func getAll() -> [Article]
    func get( identifier:Int ) -> Article?
    func create( article:Article ) -> Bool
    func update( article:Article ) -> Bool
    func delete( article:Article ) -> Bool
}

to this

protocol Repository {

  associatedtype T

  func getAll() -> [T]
  func get( identifier:Int ) -> T?
  func create( a:T ) -> Bool
  func update( a:T ) -> Bool
  func delete( a:T ) -> Bool

}

I can no longer get the dependancy injection working. So If I were to try re-creating the model shown above.

class WebArticleRepository: Repository {
    func getAll() -> [Article] {
        return [Article()]
    }

    func get(identifier: Int) -> Article? {
        return Article()
    }

    func create(a: Article) -> Bool {
        return true
    }

    func update(a: Article) -> Bool {
        return false
    }

    func delete(a: Article) -> Bool {
        return true
    }
}

class ArticleFeedViewModel {
    let articleRepo:Repository
    init( articleRepo:Repository = WebArticleRepository() ) {
        self.articleRepo = articleRepo
    }
}

This no longer works. I now get an error saying

Protocol 'Repository' can only be used as a generic constraint because it has Self or associated type requirements

Any ideas on what I am doing wrong here. It seems adding associatedType causes this to stop working. I would really like to get this functionality working as I want to be able to inject either local or web based repository pattern depending on current state of the app

Any help would be much appriecated

like image 518
AdamM Avatar asked Mar 07 '23 01:03

AdamM


1 Answers

You need to make everything else generic as well:

protocol Repository {

    associatedtype RepositoryType

    func getAll() -> [RepositoryType]
    func get( identifier:Int ) -> RepositoryType?
    func create( a:RepositoryType ) -> Bool
    func update( a:RepositoryType ) -> Bool
    func delete( a:RepositoryType ) -> Bool

}

class WebArticle { }

class WebArticleRepository: Repository {
    typealias RepositoryType = WebArticle

    func getAll() -> [WebArticle] {
        return [WebArticle()]
    }

    func get(identifier: Int) -> WebArticle? {
        return WebArticle()
    }

    func create(a: WebArticle) -> Bool {
        return true
    }

    func update(a: WebArticle) -> Bool {
        return false
    }

    func delete(a: WebArticle) -> Bool {
        return true
    }
}

class ArticleFeedViewModel<T : Repository> {
    let articleRepo: T
    init( articleRepo: T) {

        self.articleRepo = articleRepo
    }
}

// you cannot have the optional parameter in the init, instead, you can extract the following line to a method
ArticleFeedViewModel(articleRepo: WebArticleRepository())

In Swift you can't use a protocol with associated types as the type of a property/parameter etc. It's supposed to make your code more type-safe.

like image 86
Sweeper Avatar answered Mar 15 '23 17:03

Sweeper