How to create protocol-oriented generic services? - ios

I'm trying to create a protocol that will serve data for view controllers. I'm trying to take the protocol approach and makes things flexible, so the view controllers can conform using any type of data.
However, I'm getting the error: Protocol 'Serviceable' can only be used as a generic contraint because it has Self or associated type requirements
This is what I'm trying to do:
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([Postable] -> Void)? = nil) {
print("Do something...")
}
}
struct AuthorService: Serviceable {
func get(handler: ([Authorable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Postable {
var title: String { get set }
var content: String { get set }
}
protocol ServiceControllable: class {
var service: Serviceable { get } // Error: Protocol 'Serviceable' can only be used as a generic contraint because it has Self or associated type requirements
}
extension ServiceControllable {
func setupDataSource() {
service.get { items in
// Do something
}
}
}
class MyViewController: ServiceControllable {
let service: Serviceable = PostService() // Error: Same as above
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
}
}
How do I set this up so that my view controllers can implement ServiceControllable and have access to a generic setupDataSource that populates tables, collections, etc?

You want something like this.
import UIKit
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([Postable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Authorable {}
struct AuthorService: Serviceable {
func get(handler: ([Authorable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Postable {
var title: String { get set }
var content: String { get set }
}
protocol ServiceControllable: class {
// THIS is the way to use generic-constraint-protocols in protocols.
associatedtype _Serviceable: Serviceable
var service: _Serviceable { get }
}
extension ServiceControllable {
func setupDataSource() {
service.get { items in
// Do something
}
}
}
class MyViewController: UIViewController, ServiceControllable {
let service = PostService()
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
}
}
The related documentation section: Protocol Associated Type Declaratio.

Related

Add a generic delegate to a base class in Swift

Ideally, I want to create a BaseViewController class that takes in a protocol type (of a delegate) and have a weak variable as the delegate. Something like this:
class BaseViewController<Delegate: AnyObject> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
}
}
And then inherit from a view controller like so:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class SomeViewController: BaseViewController<MyDelegate> {
func doSomething() {
delegate?.funcA()
}
}
This doesn't work as the compiler complains:
'BaseViewController' requires that 'MyDelegate' be a class type
How can I work this around to achieve what I need?
Thanks in advance :)
Thats because in swift protocols doesn't confirm to them selves, you can't use "MyProtocol" as concrete type confirming to protocol "MyDelegate"
What you can rather do is
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class BaseViewController<Delegate: MyDelegate> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
EDIT 1:
As OP mentioned in comment, he is trying to introduce a generic property in BaseViewController that will simply hold a weak reference to any instance whose class is decided/declared by Child classes of BaseViewController using generics, I am simplifying the above answer a bit
Try this
protocol MyDelegate {
func funcA()
func funcB()
}
class BaseViewController<Delegate> where Delegate: AnyObject {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
protocol MyDelegate2 {
func funcABCD()
}
class SomeOtherDelegateClass2: MyDelegate2 {
func funcABCD() {
//some code here
}
}
class SomeViewController2: BaseViewController<SomeOtherDelegateClass2> {
func doSomething() {
self.delegate?.funcABCD()
}
}
TBH, I really dont see much of benefit of this design! Probably you need to revisit the code structure and see if you can come up with better code structure :)
You should set your delegate as a constraint for the generic type T in BaseViewController:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class Delegated1: MyDelegate {
func funcA() { print("A1") }
func funcB() {}
}
class Delegated2: MyDelegate {
func funcA() { print("A2") }
func funcB() {}
}
class BaseViewController<T: MyDelegate>: UIViewController {
var delegate: T?
func doSomething() {
delegate?.funcA()
}
}
class SomeViewController1: BaseViewController<Delegated1> {}
class SomeViewController2: BaseViewController<Delegated2> {}
class TestClass {
let viewController1: SomeViewController1 = {
let viewController = SomeViewController1(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
let viewController2: SomeViewController2 = {
let viewController = SomeViewController2(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
// prints:
// A1
// A2
func myFunc() {
viewController1.doSomething()
viewController2.doSomething()
}
}

Passing nil to a function with an optional generically constrained parameter in a protocol extension

I have a situation where I declare two functions in a protocol, one of them takes an optional generically constrained parameter, the other function takes no parameters, but needs to be implemented in an extension as a default function where it calls the one with a parameter and passes nil. However I get this error:
Argument passed to call that takes no arguments
My code:
public protocol MenuAccessible {
var menuEntryViewController: UIViewController { get }
}
public protocol MenuTabBarControllerProtocol {
func reloadTabs<T>(from uiProvider: T?) where T: MenuAccessible
func reloadTabs()
}
public extension MenuTabBarControllerProtocol {
func reloadTabs() {
reloadTabs(from: nil) // error here, can't infer type
}
}
Obviously the compiler is not able to infer the type. If I for example pass a nil (Optional) of the required type, then the compiler is happy. For example:
struct MenuAccessibleObject: MenuAccessible {
var menuEntryViewController: UIViewController { return UIViewController() }
}
public extension MenuTabBarControllerProtocol {
func reloadTabs() {
let menuAccessible: MenuAccessibleObject? = nil
reloadTabs(from: menuAccessible) // passes nil, but compiler is happpy
}
}
Is there a way to pass nil in my default function implementation and not have to create that dummy object?
I don't understand why you are Using Generic T there if you are defining type is MenuAccessible
Following is simply compiler without any issue
public protocol MenuAccessible {
var menuEntryViewController: UIViewController { get }
}
public protocol MenuTabBarControllerProtocol {
func reloadTabs(from uiProvider: MenuAccessible?)
func reloadTabs()
}
public extension MenuTabBarControllerProtocol {
func reloadTabs() {
reloadTabs(from: nil)
}
}
public extension MenuTabBarControllerProtocol {
func reloadTabs(from uiProvider: MenuAccessible?) {
fatalError() // implement me
}
}
EDIT
I don't know this will work for you or not but try this
public protocol MenuAccessible {
var menuEntryViewController: UIViewController { get }
}
public class UIProvider:NSObject {
}
public protocol MenuTabBarControllerProtocol {
func reloadAllTheItems<T>(from uiProvider: T?) where T: UIProvider, T: MenuAccessible
func reloadTabs()
}
public extension MenuTabBarControllerProtocol {
func reloadTabs() {
self.reloadAllTheItems(from: Temp())
}
func reloadAllTheItems (provider:(UIProvider & MenuAccessible)) {
}
}
class Temp: (UIProvider & MenuAccessible) {
var menuEntryViewController: UIViewController {
return UIViewController()
}
}

Swift: typaliasing with a protocol?

I'm struggling trying to modelize something that seems basic to me.
Let's consider a fictive service/multicast delegate implementation :
protocol Service { }
protocol Delegate { }
protocol Service1Delegate: Delegate {
func doSomething()
}
protocol Service1: Service {
func foo()
var delegates: [Service1Delegate] { get }
func register(delegate: Service1Delegate)
}
class MyService1: Service1 {
func foo() {
delegates.forEach { $0.doSomething() }
}
private(set) var delegates = [Service1Delegate]()
func register(delegate: Service1Delegate) {
self.delegates.append(delegate)
}
}
class A: Service1Delegate {
func doSomething() {
print("Hello A")
}
}
class B: Service1Delegate {
func doSomething() {
print("Hello B")
}
}
let service1 = MyService1()
service1.register(delegate: A())
service1.register(delegate: B())
service1.foo()
No problem, I get Hello A and Hello B printed.
Now consider a second service
protocol Service2Delegate: Delegate {
func doSomethingElse()
}
protocol Service2: Service {
func bar()
var delegates: [Service2Delegate] { get }
func register(delegate: Service2Delegate)
}
class MyService2: Service2 {
func bar() {
delegates.forEach { $0.doSomethingElse() }
}
private(set) var delegates = [Service2Delegate]()
func register(delegate: Service2Delegate) {
self.delegates.append(delegate)
}
}
Service1 and Service2 both have some code in common relative to delegates that I wish to be made generic.
So I'm introducing a new protocol Delegable like so:
protocol Delegable {
associatedtype D: Delegate
var delegates: [D] { get}
func register(delegate: D)
}
typealias DelegableService = Service & Delegable
and I'd define services like this now
protocol Service1: DelegableService {
func foo()
}
protocol Service2: DelegableService {
func bar()
}
However when implementing them and specifying their delegate type with a typealias, the compiler complains that my implementation do not conform to Delegable
class MyService1: Service1 {
typealias D = Service1Delegate // <- the compiler does not like this, because it's not a concrete type
func foo() {
print("foo")
}
private(set) var delegates = [Service1Delegate]()
func register(delegate: Service1Delegate) {
self.delegates.append(delegate)
}
}
It seems we can only typealias with a concrete type, not a protocol. But the concrete types are unknown for now, and will be of different nature (class A and class B), but they all conform to Service1Delegate.
Is there a solution any way to do that in Swift ?
Not positive I follow your desired outcome, but try this
struct delegateList<T> {
var items: [T]
mutating func add(item: T){
items.append(item)
}
}
protocol Delegable {
var delegates: delegateList<someProtocol>
func register(delegate: someProtocol)
}

Proper way of setting delegates in MVVM

I would like to know what is a proper way of setting delegates in the ViewModel in MVVM pattern in Swift.
I'm instantiating the ViewController from another class:
let viewModel = DashboardViewModel()
let viewController = DashboardViewController(viewModel: viewModel)
My ViewModel:
protocol DashboardViewModelType {
var items: [Item] { get }
var reloadDelegate: DashboardDataReloadDelegate? { get set }
}
protocol DashboardDataReloadDelegate: class {
func reloadData()
}
class DashboardViewModel: DashboardViewModelType {
var items: [Item] = []
weak var reloadDelegate: DashboardDataReloadDelegate?
init() {
loadItems()
}
func loadItems() {
let databaseFetcher = DatabaseDaysFetcher()
databaseFetcher.getDays(onData: { (items) in
self.items = items
reloadDelegate?.reloadData() //delegate is nil here
}) { (error) in
print(error)
}
}
}
and ViewController:
class DashboardViewController: UIViewController {
var viewModel: DashboardViewModelType?
init(viewModel: DashboardViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel!.reloadDelegate = self // it is executed after
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension DashboardViewController: DashboardDataReloadDelegate {
func reloadData() {
print("data reloaded")
}
}
So the main problem is that if I want to inject the viewModel in another class I'm instantiating the viewModel when delegate is not yet set. Would it be better to declare loadItems inside the DashboardViewModelType protocol and then call this function from the init or viewDidLoad inside the ViewController?
Yes, you could inject DatabaseDaysFetcher in the init for the DashboardViewModel and then as you say, move loadItems to the DashboardViewModelType protocol.
Then when you call loadItems, it should callback in to the caller.
Then use [weak self] in the loadItems callback.
This would negate the need for the delegate.
protocol DashboardViewModelType {
init(databaseFetcher: DatabaseDaysFetcher)
func loadItems(completion: ([Item]) -> Void, error: (Error) -> Void)
}
final class DashboardViewModel: DashboardViewModelType {
private var databaseFetcher: DatabaseDaysFetcher
init(databaseFetcher: DatabaseDaysFetcher) {
self.databaseFetcher = databaseFetcher
}
func loadItems(completion: ([Item]) -> Void, onError: (Error) -> Void) {
self.databaseFetcher.getDays(onData: { (items) in
completion(items)
}) { (error) in
onError(error)
}
}
}

Swift closure in protocol extension

I want to Decorate UIViewController with the ability to adjust it's interface when setInteractionEnabled method is called from another class (ex. Network State Manager). All changes (if any) should be provided in the concrete controller by overriding onInteractionChanged. Here is my code:
import Foundation
typealias InteractionClosure = ((enabled: Bool) -> Void)
protocol Interaction: class {
var onInteractionChanged: InteractionClosure? { get set }
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
// Default: Do nothing
// Throws: - Extensions may not contain stored properties
var onInteractionChanged: InteractionClosure? = nil
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
}
extension UIViewController : Interaction {}
How to add default implementation for onInteractionChanged?
Answering my own question is something usually I don't do, but here is my solution:
typealias InteractionClosure = (enabled: Bool) -> Void
protocol Interaction: class {
func addOnInteractionChanged(closure: InteractionClosure)
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
func addOnInteractionChanged(closure: InteractionClosure) {
onInteractionChanged = closure
}
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
// MARK: - Private
private var onInteractionChanged: InteractionClosure? {
get {
let wrapper =
objc_getAssociatedObject(self, &icAssociationKey) as? ClosureWrapper
return wrapper?.closure
}
set(newValue) {
objc_setAssociatedObject(self,
&icAssociationKey,
ClosureWrapper(newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController : Interaction {}
// Helpers
private var icAssociationKey: UInt8 = 0
private class ClosureWrapper {
var closure: InteractionClosure?
init(_ closure: InteractionClosure?) {
self.closure = closure
}
}
Client class:
class LoginViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
}
// MARK: - Private
private func setup() {
// ...
addOnInteractionChanged { [unowned self] (enabled) in
self.signInButton.enabled = enabled
self.activityIndicatorView.hidden = !enabled
}
}
}
In manager class:
visibleViewController?.setInteractionEnabled(true)
If you would like property to have only { get } ability, you can use:
protocol TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? { get }
}
extension TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? {
return { parameter in
print(parameter)
}
}
}

Resources