9
votes

A WWDC 2015 session video describes the idea of Protocol-Oriented Programming, and I want to adopt this technique in my future apps. I've been playing around with Swift 2.0 for the last couple of days in order to understand this new approach, and am stuck at trying to make it work with the Delegate Pattern.

I have two protocols that define the basic structure of the interesting part of my project (the example code is nonsense but describes the problem):

1) A delegation protocol that makes accessible some information, similar to UITableViewController's dataSource protocol:

protocol ValueProvider {
    var value: Int { get }
}

2) An interface protocol of the entity that does something with the information from above (here's where the idea of a "Protocol-First" approach comes into play):

protocol DataProcessor {
    var provider: ValueProvider { get }
    func process() -> Int
}

Regarding the actual implementation of the data processor, I can now choose between enums, structs, and classes. There are several different abstraction levels of how I want to process the information, therefore classes appear to fit best (however I don't want to make this an ultimate decision, as it might change in future use cases). I can define a base processor class, on top of which I can build several case-specific processors (not possible with structs and enums):

class BaseDataProcessor: DataProcessor {
    let provider: ValueProvider

    init(provider: ValueProvider) {
        self.provider = provider
    }

    func process() -> Int {
        return provider.value + 100
    }
}

class SpecificDataProcessor: BaseDataProcessor {
    override func process() -> Int {
        return super.process() + 200
    }
}

Up to here everything works like a charm. However, in reality the specific data processors are tightly bound to the values that are processed (as opposed to the base processor, for which this is not true), such that I want to integrate the ValueProvider directly into the subclass (for comparison: often, UITableViewControllers are their own dataSource and delegate).

First I thought of adding a protocol extension with a default implementation:

extension DataProcessor where Self: ValueProvider {
    var provider: ValueProvider { return self }
}

This would probably work if I did not have the BaseDataProcessor class that I don't want to make value-bound. However, subclasses that inherit from BaseDataProcessor and adopt ValueProvider seem to override that implementation internally, so this is not an option.

I continued experimenting and ended up with this:

class BaseDataProcessor: DataProcessor {
    // Yes, that's ugly, but I need this 'var' construct so I can override it later
    private var _provider: ValueProvider!
    var provider: ValueProvider { return _provider }

    func process() -> Int {
        return provider.value + 10
    }
}

class SpecificDataProcessor: BaseDataProcessor, ValueProvider {
    let value = 1234

    override var provider: ValueProvider { return self }

    override func process() -> Int {
        return super.process() + 100
    }
}

Which compiles and at first glance appears to do what I want. However, this is not a solution as it produces a reference cycle, which can be seen in a Swift playground:

weak var p: SpecificDataProcessor!
autoreleasepool {
    p = SpecificDataProcessor()
    p.process()
}
p // <-- not nil, hence reference cycle!

Another option might be to add class constraints to the protocol definitions. However, this would break the POP approach as I understand it.

Concluding, I think my question boils down to the following: How do you make Protocol Oriented Programming and the Delegate Pattern work together without restricting yourself to class constraints during protocol design?

1

1 Answers

3
votes

It turns out that using autoreleasepool in Playgrounds is not suited to proof reference cycles. In fact, there is no reference cycle in the code, as can be seen when the code is run as a CommandLine app. The question still stands whether this is the best approach. It works but looks slightly hacky.

Also, I'm not too happy with the initialization of BaseDataProcessors and SpecificDataProcessors. BaseDataProcessors should not know any implementation detail of the sub classes w.r.t. valueProvider, and subclasses should be discreet about themselves being the valueProvider.

For now, I have solved the initialization problem as follows:

class BaseDataProcessor: DataProcessor {  
    private var provider_: ValueProvider! // Not great but necessary for the 'var' construct  
    var provider: ValueProvider { return provider_ }  

    init(provider: ValueProvider!) {  
        provider_ = provider  
    }  

    func process() -> Int {  
        return provider.value + 10  
    }  
}  

class SpecificDataProcessor: BaseDataProcessor, ValueProvider {  
    override var provider: ValueProvider { return self } // provider_ is not needed any longer  

    // Hide the init method that takes a ValueProvider  
    private init(_: ValueProvider!) {  
        super.init(provider: nil)  
    }  

    // Provide a clean init method  
    init() {  
        super.init(provider: nil)  
        // I cannot set provider_ = self, because provider_ is strong. Can't make it weak either  
        // because in BaseDataProcessor it's not clear whether it is of reference or value type  
    }  

    let value = 1234  
}

If you have a better idea, please let me know :)