0

I'm trying to factorize methods using generics in Swift. My methods are as follow:

func searchFoo(searchText: String?, completion: @escaping ([Foo]?, Error?) -> ()) {
    let index = "foo" // <- How can I assign this from a generic type?
    searchClient.search(index, searchText) { (json, error) in
        if let json = json {
            completion(self.fooFrom(json: json), nil)
        } else if let error = error {
            completion(nil, error)
        } else {
            completion([], nil)
        }
    }
}

func searchBar(searchText: String?, completion: @escaping ([Bar]?, Error?) -> ()) {
    let index = "bar" // <- How can I assign this from a generic type?
    searchClient.search(index, searchText) { (json, error) in
        if let json = json {
            completion(self.barFrom(json: json), nil)
        } else if let error = error {
            completion(nil, error)
        } else {
            completion([], nil)
        }
    }
}

These 2 functions look like a good usecase for generics but I can't figure out how I could switch on a generic type to get the 2 required strings.

Of course, I could put the index name in the function signature, but I feel that it would be redundant as the index name is implied by the generic type actually being used and it could be a source of bugs.

I feel like I'm missing something as I may not think the Swift way yet, so any help would be welcome to get a good refactoring for this code.

Mick F
  • 7,312
  • 6
  • 51
  • 98
  • 1
    Maybe try rewriting the method as generic as possible and just leave the bit out where your actual question is. Right now it took me quite some time to understand where generics even could be of any use here. Now my question is in particular how you think about dealing with `fooFrom` and `barFrom` since they are not generic at all. Regarding getting the name of the generic type [this](http://stackoverflow.com/a/29879223/2442804) *might* be helpful – luk2302 Mar 22 '17 at 11:30
  • Good point. I'll edit @Tim's answer to reflect this point. – Mick F Mar 22 '17 at 11:42

1 Answers1

2

You can achieve this by creating a protocol:

protocol Baz {
    static var index: String { get }
    static func loadFromJSON(json: JSONObject) -> [Self]
}

Then extend Foo and Bar to conform to this protocol. Finally, add a generic constraint to your search function:

func search<T: Baz>(searchText: String?, completion: @escaping ([T]?, Error?) -> ()) {
    searchClient.search(T.index, searchText) { (json, error) in
        guard error == nil else { completion(nil, error!) }

        if let json = json {
            completion(Baz.loadFromJSON(json: json), nil)
        } else {
            completion([], nil)
        }
    }
}
Mick F
  • 7,312
  • 6
  • 51
  • 98
Tim Vermeulen
  • 12,352
  • 9
  • 44
  • 63