john doe
john doe

Reputation: 9660

Conforming to Protocols in Swift Using Extensions

I have a Swift protocol defined as follows:

protocol SmartContract {

    func apply(transaction :Transaction)
    func addBrokenRule(_ brokenRule: BrokenRule)
    var brokenRules :[BrokenRule] { get set }
} 

I have an extension on SmartContract defined as follows:

extension SmartContract {

    mutating func addBrokenRule(_ brokenRule :BrokenRule) {

        if self.brokenRules == nil {
            self.brokenRules = [BrokenRule]()
        }

        self.brokenRules.append(brokenRule)
    }
}

I also have a class MoneyTransferContract which conforms to the protocol but does not define brokenRules. This is because I have defined the brokenRules inside the extension.

class MoneyTransferContract : SmartContract { 

     func apply(transaction :Transaction) { // do something here }
}

My question is how can I make sure that MoneyTransformContract conforms to the SmartContract protocol. Is there anyway to have BrokenRule available to MoneyTransformContract without defining it again and again in different SmartContracts.

Upvotes: 2

Views: 84

Answers (2)

Jeff
Jeff

Reputation: 4209

john doe wrote:

Is there anyway to have BrokenRule available to MoneyTransformContract without defining it again and again in different SmartContracts.

What you want is a superclass/subclass relationship for that behavior.

class SmartContract {
    func apply(transaction :Transaction) {
        //implemention
    }
    var brokenRules: [BrokenRule] = []
    func addBrokenRule(_ brokenRule :BrokenRule) {
        brokenRules.append(brokenRule)
    }
}

class MoneyTransferContract : SmartContract {
    // Gets `brokenRules` for free from superclass.
}

class BitCoinTransferContract : SmartContract { 
    // Gets `brokenRules` for free from superclass.
}

Upvotes: 2

Jumhyn
Jumhyn

Reputation: 6777

If I'm understanding correctly, there is no way to do what you want. (EDIT: as Jeff notes, if you want to use inheritance as opposed to a protocol, this is possible. See his answer for how). Protocols in Swift are just lists of requirements that it is up to implementing types to properly define. Protocols generally don't have any say over the actual behavior or implementation of implementing types, and only guarantees that the properties and functions exist. Your SmartContract protocol says that every SmartContract must have a function apply(transaction:), a function addBrokenRule(_:), and a property brokenRules which can be accessed and modified.

When a type implements SmartContract it has to define each one of these. Just as you have to write out the signature to tell the compiler that you are implementing apply(transaction:), you also have to tell the compiler how the property brokenRules will be implemented. You could obtain this functionality in a somewhat useless way, by defining an extension which has brokenRules as a computed property which is essentially a no-op:

extension SmartContract {
    var brokenRules: [BrokenRule] {
        get { return [] }
        set(newRules) { }
    }
}

But this means that any implementing type which forgets to specify their own implementation for brokenRules will have a brokenRules property which always resolves to an empty array.

One reason for this is Swift's computed properties. For some implementing type, it might not make sense for brokenRules to be stored as an array--and a protocol can't force that. That's an implementation detail that a protocol author can't (and shouldn't) worry about. For instance, if BrokenRules were easily convertible to and from strings, you could imagine some class which implemented SmartContract like so:

class StringContract: SmartContract {
    var ruleString: String
    var brokenRules: [BrokenRule] {
        get {
            let stringArray = ruleString.split(separator: ",")
            return stringArray.map { BrokenRule(string:String($0)) }
        }
        set(newRules) {
            let stringArray = newRules.map { $0.stringValue }
            ruleString = stringArray.joined(separator: ",")
        }
    }

    func apply(transaction: Transaction) {
        // do something here...
    }

    init() {
        ruleString = ""
    }
}

Note that we don't have to specify an implementation for addBrokenRule(_:). That's what your protocol extension gets you. By providing an implementation in the extension, you ensure that all implementing types have a default implementation of the function, and so they can choose to forego defining their own.

Upvotes: 1

Related Questions