Dareon
Dareon

Reputation: 384

How to extend a function of a protocol in swift 3?

I have following code:

protocol Protocol_1 {
    var prop_1: String { get set }
    var prop_2: Int { get set }
}

protocol Protocol_2 {
    var prop_3: Double { get set }
    var prop_4: Bool { get set }
}

extension Protocol_1 {
    mutating func set<T>(value: T, forKey key: String)
    {
        switch key {
        case "prop_1":
            if value is String {
                prop_1 = value as! String
            }

        case "prop_2":
            if value is Int {
                prop_2 = value as! Int
            }

        default:
            break
        }
    }
}

extension Protocol_2 {
    mutating func set<T>(value: T, forKey key: String)
    {
        switch key {
        case "prop_3":
            if value is Double {
                prop_3 = value as! Double
            }

        case "prop_4":
            if value is Bool {
                prop_4 = value as! Bool
            }

        default:
            break
        }
    }
}

struct MyStruct : Protocol_1, Protocol_2 {
    var prop_1: String
    var prop_2: Int
    var prop_3: Double
    var prop_4: Bool
}

var myStruct = MyStruct(prop_1: "hello", prop_2: 0, prop_3: 3.5, prop_4: true)
myStruct.set(value: "bye", forKey: "prop_1")

Now the playground gives me an error because it is not clear what set function should be called. Playground execution failed: error: ProtocolsPlayground.playground:59:1: error: ambiguous use of 'set(value:forKey:)' myStruct.set(value: "bye", forKey: "prop_1")

This is clear but how can I achieve such function extension or is there a work around? Especially if the Protocol_1 is not editable.

Upvotes: 3

Views: 2038

Answers (1)

Alain T.
Alain T.

Reputation: 42143

I don't know of any way to extend an existing function or another protocol's function using a protocol.

What I would suggest is that you separate the mechanism that assigns properties by name from the two protocols using a third protocol that does that specifically and separately.

Here's one way to approach this:

Define a class that will handle getting and setting properties based on a mapping between names (keys) and variable references:

class PropertyMapper
{   
   static var sharedGetter = PropertyMapperGet()
   static var sharedSetter = PropertyMapperSet()

   var value   : Any = 0
   var success       = false
   var key           = ""

   func map<T>(_ key:String, _ variable:inout T) {}

   func clear()
   {
      value   = 0
      success = false
      key     = ""
   }
}

class PropertyMapperGet:PropertyMapper
{
   func get(forKey:String)
   {
     key     = forKey
     success = false
   }

   override func map<T>(_ key:String, _ variable:inout T) 
   {
      if !success && self.key == key 
      {
        value        = variable 
        success      = true
      }
   }

}

class PropertyMapperSet:PropertyMapper
{
   override func map<T>(_ key:String, _ variable:inout T) 
   {
      if  !success && self.key == key,
      let newValue = value as? T 
      { 
        variable     = newValue
        success      = true 
      }
   }

   func set(value newValue:Any, forKey:String)
   {
     key     = forKey
     value   = newValue
     success = false
   }
}

Then, you can define a protocol for all struct and classes that will have the ability to assign their properties by name (key):

protocol MappedProperties
{
   mutating func mapProperties(_ :PropertyMapper)
}

extension MappedProperties
{
   mutating func get(_ key:String) -> Any?
   {
      let mapper = PropertyMapper.sharedGetter
      defer { mapper.clear() }
      mapper.get(forKey:key)
      mapProperties(mapper)
      return mapper.success ? mapper.value : nil
   }

   @discardableResult
   mutating func set(value:Any, forKey key:String) -> Bool
   {
      let mapper = PropertyMapper.sharedSetter
      defer { mapper.clear() }
      mapper.set(value:value, forKey:key)
      mapProperties(mapper)
      return mapper.success
   }
}

Your protocols can require that the struct that adopt them offer the named assignments. (see farther down for making the property mapping part of your protocol)

protocol Protocol_1:MappedProperties 
{
    var prop_1: String { get set }
    var prop_2: Int    { get set }
}

protocol Protocol_2:MappedProperties 
{
    var prop_3: Double { get set }
    var prop_4: Bool   { get set }
}

Your struct will need to implement the property mapping in order to adopt your protocols. The key/variable mapping is performed in a centralized function for the whole struct and needs to provide keys for all the variables in both protocols.

struct MyStruct : Protocol_1, Protocol_2 
{
    var prop_1: String
    var prop_2: Int
    var prop_3: Double
    var prop_4: Bool

    mutating func mapProperties(_ mapper:PropertyMapper)
    {
       mapper.map("prop_1", &prop_1)
       mapper.map("prop_2", &prop_2)
       mapper.map("prop_3", &prop_3)
       mapper.map("prop_4", &prop_4)       
    }
}

This allows the struct to assign properties by name (key):

var myStruct = MyStruct(prop_1: "hello", prop_2: 0, prop_3: 3.5, prop_4: true)
myStruct.set(value: "bye", forKey: "prop_1")

To refine this further and make the property mapping part of your protocols, you can add a mapping function to the protocol so that the structs that adopt it don't have to know the details of this mapping.

They will still need to implement the mapping protocol's function but they can simply use functions provided by your protocols to do the job.

For example( I only showed Protocol_1 but you can do it with both of them):

extension Protocol_1
{
   mutating func mapProtocol_1(_ mapper:PropertyMapper)
   {
       mapper.map("prop_1", &prop_1)
       mapper.map("prop_2", &prop_2)
   }
}

With the function provided by the protocol, the struct doesn't need to know which properties are mapped. This should make maintenance of the structs and protocols less error prone and avoid duplications.

struct MyStruct : Protocol_1, Protocol_2 
{
    var prop_1: String
    var prop_2: Int
    var prop_3: Double
    var prop_4: Bool

    mutating func mapProperties(_ mapper:PropertyMapper)
    {
       mapProtocol_1(mapper)
       mapper.map("prop_3", &prop_3)
       mapper.map("prop_4", &prop_4)       
    }
}

Upvotes: 3

Related Questions