Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protocol Oriented Programming and the Delegate Pattern

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?

like image 833
ahaese Avatar asked Sep 06 '15 15:09

ahaese


People also ask

What is delegate and protocol?

Protocol: A set of methods that would be implemented by the class which conforms to that protocol. Delegate: The reference to that class which conforms to the protocol and will adhere to implement methods defined in the protocol.

What is protocol oriented programming?

Protocol-Oriented Programming is a new programming paradigm ushered in by Swift 2.0. In the Protocol-Oriented approach, we start designing our system by defining protocols. We rely on new concepts: protocol extensions, protocol inheritance, and protocol compositions. The paradigm also changes how we view semantics.

What is the purpose of the delegate pattern?

The delegation pattern enables an object to use another “helper” object to provide data or perform a task rather than do the task itself. This pattern has three parts: An object needing a delegate, also known as the delegating object. It's the object that has a delegate.

What is delegate pattern in Swift?

The core purpose of the delegate pattern is to allow an object to communicate back to its owner in a decoupled way. By not requiring an object to know the concrete type of its owner, we can write code that is much easier to reuse and maintain.

What is the delegation design pattern?

In software engineering, the delegation pattern is an object-oriented design pattern that allows object composition to achieve the same code reuse as inheritance . In delegation, an object handles a request by delegating to a second object (the delegate ).

What is the use of delegate pattern in iOS?

The delegate pattern has long been very prominent on Apple's platforms. Delegation is used for everything from handling table view events using UITableViewDelegate, to modifying cache behavior using NSCacheDelegate. The core purpose of the delegate pattern is to allow an object to communicate back to its owner in a decoupled way.

What is protocol-oriented design?

With protocol-oriented design, we are encouraged to create multiple smaller protocols with very specific requirements. Let’s look at how protocol composition works. Protocol inheritance and composition are really powerful features but can also cause problems if used wrongly.

Should you use closures or delegate protocols?

Using delegate protocols provide a familiar and solid pattern that is a good default for many use cases. Closures add more flexibility, but can also lead to more complicated code (not to mention accidental retain cycles if the delegating object ends up capturing its owner in one of its closures).


1 Answers

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 :)

like image 50
ahaese Avatar answered Sep 18 '22 01:09

ahaese