3

Hi I am new to swift and writing iOS application. I would like to know how can I better use generics to pass my result data.

I have one delegate which gives data of person from server

protocol PersonDataProvider {
    func dataReceived(_ result: PersonResult)
}

Result can have success and fail status through enumeration.

enum PersonResult {
    case success
    case networkFailed
}

Below class calls server api and fetch data, passes back

class MyNetworkClass {

    var personDataProvider: PersonDataProvider
    func getDataFromServer() {
        personDataProvider.dataReceived(.success)
    }
}

Below is viewcontroller where i subscribe provider

class MyViewController: PersonDataProvider {

    func dataReceived(_ result: PersonResult) {

        switch result {
        case .success:
            print("success")

        case .networkFailed:
            print("no network")

        }
    }

}

Now, I would like send extra information with success block which can be anything like below data model. It may have any type.

class Employer:Person {
    let id
    let salary
}

class Student:Person {
    let id
    let rollNumber
}

How can I achieve that? Can i define associate type in protocol and achieve if yes how?

How can I subscriber "MyViewController" of "PersonDataProvider" can define type of result he expects from "success" block?

Kapil
  • 133
  • 2
  • 11
  • Why do you imagine _generics_ are involved? And where in your code do you “send” a “success block”? – matt Dec 16 '18 at 04:36
  • "getDataFromServer" calls delegate method. That is place from where I want to pass "Employer" or "Student" data based on requirement as parameter via success block. I am not sure how i can achieve it. I also want "MyNetworkClass" will define want type data he wants through "PersonDataProvider" when he subscribes – Kapil Dec 16 '18 at 04:44
  • Ok, can Employer and Student have a common superclass? So then success could have an associated value which might be either one. – matt Dec 16 '18 at 05:06
  • @matt Yes it can have. How to use associated value in that case? – Kapil Dec 16 '18 at 05:07

2 Answers2

1

Use an associated value as part of the .success case.

Let's say you have a superclass Person for your types of people. Then define your enum like this:

enum PersonResult {
    case success(Person)
    case networkFailed
}

Now when you hand back a .success value you must supply a Person to go with it:

func getDataFromServer() {
    let person = Student.init(...) // initialize, or fetch, or whatever
    personDataProvider.dataReceived(.success(person))
}

Here is a complete fake example that mocks up the whole thing; you'll have to change the details, but the point is, this compiles:

enum PersonResult {
    case success(Person)
    case networkFailed
}
protocol PersonDataProvider {
    func dataReceived(_ result: PersonResult)
}
class MyNetworkClass {
    var personDataProvider: PersonDataProvider!
    func getDataFromServer() {
        let person = Student.init(id: 3, rollNumber: 10)
        personDataProvider.dataReceived(.success(person))
    }
}
class Person {}
class Employer:Person {
    let id : Int
    let salary : Int
    init(id:Int, salary:Int) {
        self.id = id; self.salary = salary
    }
}
class Student:Person {
    let id : Int
    let rollNumber : Int
    init(id:Int, rollNumber:Int) {
        self.id = id; self.rollNumber = rollNumber
    }
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks for above suggestions. I am not clear yet with associated return type. Is there any way subscriber of "PersonDataProvider" i.e "MyViewController" can define what type of data it is expecting from success block. – Kapil Dec 16 '18 at 05:32
0

You have to move away from delegation pattern to achieve something like what you want. Instead you will need a generic enum of Result type and your networking class which is generic to your data model type.

enum Result<T> {
    case success(T)
    case networkFailed
}

class MyNetworkClass<T: Person> {
    func getDataFromServer(completion: @escaping (Result<T>)-> Void) {
        // here goes your implementation (data fetching)
        // for demonstration purpose I just initialized object
        if let student = Student(id: 112233, rollNumber: 25) as? T {
            completion(Result.success(student))
        } else if let employer = Employer(id: 445566, salary: 5200) as? T {
            completion(Result.success(employer))
        } else {
            completion(Result.networkFailed)
        }
    }
}

Then your usage would be:

MyNetworkClass().getDataFromServer { (result: Result<Student>) in // you can also use Employer type
    switch result {
    case .success(let student):
        print(student.id)
    case .networkFailed:
        print("Error")
    }
}
nayem
  • 7,285
  • 1
  • 33
  • 51
  • Thanks @nayem for suggestion. How can i use associated type here so that "MyViewController" can tell which type of data he is expecting from "PersonDataProvider"?? – Kapil Dec 17 '18 at 22:38
  • 1
    Are you referring to ___Protocol with Associated Type (PAT)___? I think that is over complication and is not suited to the requirements. Though I've not work with _PATs as in `delegate`_ you might want to look at these: [here](https://stackoverflow.com/q/47612406/3687801), [here](https://stackoverflow.com/q/25732775/3687801) or [here](https://stackoverflow.com/q/50075977/3687801). – nayem Dec 18 '18 at 02:42