swiftgenerics

Generic method to update different data type properties in Swift


I don't if its possible or not, but I think there can be a way to do what I want.
I have following situation:

enum ContactStatus: UInt {
    case requestSent = 0, requestReceived, requestProcessing, active, removed
}

struct Contact {
    var uid: String
    var name: String?
    var contactStatus: ContactStatus

    init(uid: String, name: String? = nil, contactStatus: ContactStatus = .requestSent) {
        self.uid = uid
        self.name = name
        self.contactStatus = contactStatus
    }
}

class ContactStorage: {
    private var contacts: [Contact]
    init(contacts: [Contact]) {
        contacts = contacts
    }

    func createContact(uid: String, contactStatus status: ContactStatus, name: String?) -> Contact {
        var newContact = Contact(uid: uid)
        newContact.contactStatus = status
        newContact.name = name
        newContacts.append(newContact)
        return newContact
    }
    func updateContact(status: ContactStatus, peerUId: String) {
        for (index, _) in contacts.enumerated() {
            if contacts[index].uid == peerUId {
                contacts[index].contactStatus = status
                return
            }
        }
    }
    func updateContact(name: String, peerUId: String) {
        for (index, _) in contacts.enumerated() {
            if contacts[index].uid == peerUId {
                contacts[index].name = name
                return
            }
        }
    }
}

As we can see, ContactStorage has two methods to update contact status and name.
I am looking to merge these two methods in one using generic data types or any other possible way.

Something like:

func updateContact<T: Updateable>(peerUId: String, updatableProperty: T) {
    //implementation
}

Is it possible to write only one method instead of two?


Solution

  • You can "kind of" do this with closures.

    The "XXX" and "YYY" part is the only part that is different between the two methods you have:

    for (index, _) in contacts.enumerated() {
        if contacts[index].uid == peerUId {
            contacts[index].XXX = YYY
            return
        }
    }
    

    So, if we want to extract this as a method, we need a closure as parameter to provide the "XXX" part.

    That closure can look like this:

    (inout Contact, T) -> Void
    

    For the "YYY" part, a regular parameter will do just fine.

    So, the method looks like this:

    func updateProperty<T>(property: (inout Contact, T) -> Void, value: T, peerUId: String) {
        for (index, _) in contacts.enumerated() {
            if contacts[index].uid == peerUId {
                property(&contacts[index], value)
                return
            }
        }
    }
    

    You would call it like this:

    updateProperty(property: {$0.name = $1}, value: "Tom", peerUId: "something")
    

    Another way to do this is to remove "YYY" completely, and integrate it into the closure:

    func updateProperty(property: (inout Contact) -> Void, peerUId: String) {
        for (index, _) in contacts.enumerated() {
            if contacts[index].uid == peerUId {
                property(&contacts[index])
                return
            }
        }
    }
    

    Usage:

    updateProperty(property: {$0.name = "Tom"}, peerUId: "something")