Swift: Sharing logic tied to state when composition isn't a good option - ios

Let's say you have two classes conforming to a protocol and you want some logic to be shared between them. In languages like Java, you'd typically create an abstract class with the shared logic and make use of it in subclasses. In Swift, abstract classes aren't supported. What's the recommended approach for accomplishing this?
One answer is composition, but what if the common functionality can't be divided into smaller components in a clean and sensible way?
Another answer is to implement common functionality in the protocol itself, but what if it's heavily tied to state?
Some context:
I'm working on an iOS app in which two screens use the same view for different purposes. I'm using the MVP pattern and would like to share common logic among the two presenters. There is state involved, and there isn't really a clean way to pull shared logic into separate components since it's so closely tied to the view interface.
Here's a minimal example of this situation:
protocol View {
func doSomething()
}
class ViewController : UIViewController, View {
func doSomething() { }
}
protocol Presenter {
func tellViewToDoSomething()
}
struct Presenter1 : Presenter {
let view: View
init(withView view: View) {
self.view = view
// then do something unique to presenter 1
}
func tellViewToDoSomething() {
view.doSomething()
// then do something unique to presenter 1
}
}
struct Presenter2 : Presenter {
let view: View
init(withView view: View) {
self.view = view
// then do something unique to presenter 2
}
func tellViewToDoSomething() {
view.doSomething()
// then do something unique to presenter 2
}
}
I'm asking this as a general question rather than in terms of my current situation because I'd like to understand general approaches for sharing common logic in Swift.
I'm coming from an OOP background and it's likely that I'm fundamentally misunderstanding something, so maybe someone could enlighten me.

Could a protocol extension help here? The thing that makes protocol-oriented programming tricky from an OOP perspective, of course, is that there is no super. But nothing stops you from just calling the protocol's built-in functionality:
protocol View { func doSomething() }
protocol Presenter {
var view : View {get set}
}
extension Presenter {
func tellViewToDoSomething() {
self.view.doSomething()
}
}
struct Presenter1 : Presenter {
var view: View
func tellViewToDoSomethingAndThenSome() {
self.tellViewToDoSomething()
// and then some
}
}

Related

Is a blank function conventional in subclass that conforms to custom protocol?

I have two main screens in my app, currently both just subclasses of UIViewController. These two view controllers are very similar - they both implement my custom subclass of UIView called HeaderView that is responsible for displaying information and taking user input. As it stands, this code is repetitive because the HeaderView setup is the same for both view controllers - the only difference is what happens when the user confirms the text entry in HeaderView.
To cut down on repetitive code, I am creating a class called InputViewController (a subclass of UIViewController) that houses the aspects of the two view controllers that are identical. Eventually, I want the two view controllers to subclass InputViewController instead of UIViewController.
class InputViewController: UIViewController, InputProtocol {
private let headerView = HeaderView()
override func viewDidLoad() {
super.viewDidLoad()
// layout, etc.
setupCallbacks()
}
internal func setupCallbacks() {
headerView.onUpdate = { (text: String) in
// called when user confirms text entry in headerView
self.onHeaderUpdate()
}
}
internal func onHeaderUpdate() {} // Blank function
}
setupCallbacks() and onHeaderUpdate() are methods defined in the protocol that the InputViewController conforms to. The HeaderView implements a callback closure that is handled in setupCallbacks() by headerView.onUpdate...
The protocol that InputViewController conforms to:
protocol InputProtocol {
func setupCallbacks()
func onHeaderUpdate()
}
To illustrate this, I drew up a diagram;
Since I want the subclasses of InputViewController to override the onHeaderUpdate() method, is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank or is there another solution to this?
is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank
Yes, that is called an abstract method. It is common to give it code that crashes deliberately, as a way of saying, “I exist only to be overridden in a subclass.”
(I should go further and say that what you are creating, a base view controller that carries out initial configurations that all subclasses must implement, is also normal.)

Swift: hook into lifecycle functions of UIViewController via protocol with default implementation

Is it somehow possible to have a protocol with a default implementation via protocol extension to get some code automatically executed by just implementing the protocol in various places?
Heres an example of what I mean:
protocol Greetable {
func greet()
}
extension Greetable where Self: UIViewController {
func greet() {
print("Hello ViewController")
}
func viewDidLoadMagicFunctionHookIdontKnowIfItIsPossible() {
greet()
}
}
class MyViewController: UIViewController, Greetable {}
So I want to define a protocol, give it default implementations and then hook the extension to let's say viewDidLoad() or other functions without explicitly calling the greet function by myself in every instance.
Subclassing is not possible since I want to use UITableViewController, UIViewController, UINavigationController, UISplitViewController, etc. and I cannot create one generic baseclass for all of them at once.
I also don't want to go through all of my view controllers and call greet() in viewDidLoad() because what if I decide to move the greet() to viewDidAppear? I had to go through all the controllers and move the code.
By the way, the order of when this magic function would be called would not matter at all for me, in case I have two protocols with this magic.
I'm surely not the only one who thought about something like this and there are programmers a thousand times smarter than me who surely can exactly explain why and how or why it is not possible in Swift to "hook" yourself into a certain function call.

Should ViewController-Presenter-Interactor have one to one relationship

I am reading about VIPER and my understanding is- generally a viewController is related to one presenter and one presenter talks to one Interactor.
But, what if we have master-details pages or list-detail pages. To display list of items, I would have one controller/Presenter to display list and another controller/presenter to display details. And FetchList and FetchDetail should belong to same interactor.
If these two presenters communicate to this interactor, they would have to implement both the methods FetchList and FetchDetail. And one of these two method's implementation would be empty.
You should have two separate VIPER modules: MainItems and DetailedItems.
Read this post (https://www.ckl.io/blog/best-practices-viper-architecture) and see how to use delegates to send data between VIPER modules. Note that FetchList and FetchDetail should belong to different interactor:
// 1. Declare which messages can be sent to the delegate
// ProductScreenDelegate.swift
protocol ProductScreenDelegate {
//Add arguments if you need to send some information
func onProductScreenDismissed()
func onProductSelected(_ product: Product?)
}
// 2. Call the delegate when you need to send him a message
// ProductPresenter.swift
class ProductPresenter {
// MARK: Properties
weak var view: ProductView?
var router: ProductWireframe?
var interactor: ProductUseCase?
var delegate: ProductScreenDelegate?
}
extension ProductPresenter: ProductPresentation {
//View tells Presenter that view disappeared
func onViewDidDisappear() {
//Presenter tells its delegate that the screen was dismissed
delegate?.onProductScreenDismissed()
}
}
// 3. Implement the delegate protocol to do something when you receive the message
// ScannerPresenter.swift
class ScannerPresenter: ProductScreenDelegate {
//Presenter receives the message from the sender
func onProductScreenDismissed() {
//Presenter tells view what to do once product screen was dismissed
view?.startScanning()
}
...
}
// 4. Link the delegate from the Product presenter in order to proper initialize it
// File ScannerRouter.swift
class ProductRouter {
static func setupModule(delegate: ProductScreenDelegate?) -> ProductViewController {
...
let presenter = ScannerPresenter()
presenter.view = view
presenter.interactor = interactor
presenter.router = router
presenter.delegate = delegate // Add this line to link the delegate
...
}
}
My understanding is that you have one view/view-controller and presenter per screen and then one interactor per use case, which probably means more than one per screen. This is good practice from the point of view of Single Responsibility Principle and therefore aids testing. But sometimes a concession is made and an interactor handles multiple uses cases.

How to subscribe to delegate events globally?

I have a custom delegate that triggers certain events. For context, it's a bluetooth device that fires events arbitrarily. I'd like my view controllers to optionally subscribe to these events that get triggered by the device delegate.
It doesn't make sense that each view controller conforms to the custom delegate because that means the device variable would be local and would only fire in that view controller. Other view controllers wouldn't be aware of the change. Another concrete example would be CLLocationManagerDelegate - for example what if I wanted all view controllers to listen to the GPS coordinate changes?
Instead, I was thinking more of a global delegate that all view controllers can subscribe to. So if one view controller triggers a request, the device would call the delegate function for all subscribed view controllers that are listening.
How can I achieve this architectural design? Are delegates not the right approach? I thought maybe NotificationCenter can help here, but seems too loosely typed, perhaps throwing protocols would help makes things more manageable/elegant? Any help would be greatly appreciated!
You could have an array of subscribers that would get notified.
class CustomNotifier {
private var targets : [AnyObject] = [AnyObject]()
private var actions : [Selector] = [Selector]()
func addGlobalEventTarget(target: AnyObject, action: Selector) {
targets.append(target)
actions.append(action)
}
private func notifyEveryone () {
for index in 0 ..< targets.count {
if targets[index].respondsToSelector(actions[index]) {
targets[index].performSelector(actions[index])
}
}
}
}
Naturally, you'd have to plan further to maintain the lifecycle of targets and actions, and provide a way to unsubscribe etc.
Note: Also ideal would be for the array of targets and actions to be an of weak objects. This SO question, for instance, deals with the subject.
• NotificationCenter is first solution that comes in mind. Yes, it is loosely typed. But you can improve it. For example like this:
extension NSNotificationCenter {
private let myCustomNotification = "MyCustomNotification"
func postMyCustomNotification() {
postNotification(myCustomNotification)
}
func addMyCustomNotificationObserverUsingBlock(block: () -> ()) -> NSObjectProtocol {
return addObserverForName(myCustomNotification, object: nil, queue: nil) { _ in
block()
}
}
}
• Second solution would be to create some shared object, which will store all delegates or blocks/closures and will trigger them when needed. Such object basically will be the same as using NotificationCenter, but gives you more control.

What is the right way of using Views in iOS development?

I have a customView. It has some condition like this(only example):
customView(viewsNeed: Bool)
...
if viewsNeeded {
self.addSubView(newView)
self.addSubView(newView2)
} else {
self.addSubView(newView3)
self.addSubView(newView4)
self.addSubView(newView5)
}
and then I can add this View to in my ViewController:
self.view.addSubView(customView(viewsNeeded))
What I want to know is what should I do? Write conditions like this, or make separate Views for this purpose. Something like:
View1
...
self.addSubView(newView)
self.addSubView(newView2)
View2
...
self.addSubView(newView3)
self.addSubView(newView4)
self.addSubView(newView5)
And add one of them in the ViewController:
if viewsNeeded {
self.view.addSubView(view1)
} else {
self.view.addSubView(view2)
}
What kind of View creating is better in what situation, and how should i decide this kind of things? I need some really wide answers with explanations if it's real.
If a view can have different states, you would take care of those different states within the view that has a certain responsibility. The UINavigationBar is a good example. It has a clear purpose, giving navigational context to the user, but it's state (and context) can make it appear different.
func pushNavigationItem(...) {
...
if self.items.count > 1 {
// show backButton
} else {
// hide backButton
}
}
If the different views don't work together for a shared purpose, I wouldn't group them together in a container-view, but instead add them separately, dependent on your needs in a ViewController.
override func viewDidLoad() {
if userDidBuyContent() {
// add view with bought content
} else {
// add view to buy content
}
}
And in general it's a good practice to keep your view-hierachy as flat as possible. The less views you introduce, the better your app will perform. The decision is ultimately up to you, but just keep in mind what the purpose of a view is and whether subviews contribute to that purpose or are really serving some other purpose.
there is no conceptual difference between options you've described. from MVC pattern perspective they are both slightly wrong. you don't have to add views manually, view must create its structure itself.

Resources