I am trying to make following code compile.
Basically I want to have object conforming multiple protocols, for example network service that exposes different groups of API to different parts of app (for example: login API to loginController, sharing API to shareController)
+
I want this service to be injected using networkXProvider protocols e.g. implement Dependency Injection (for example I want to pass to LoginController an Injection that adopts LoginControllerProvider and FetchingUsersFromDBProvider):
protocol MyAccountCommunicator {
func getAccountData() -> String
}
protocol EventsCommunicator {
func getEvents() -> String
}
class NetworkManager: MyAccountCommunicator, EventsCommunicator {
func getAccountData() -> String {
return "Accounts"
}
func getEvents() -> String {
return "Events"
}
}
protocol MyAccountCommunicatorProvider {
var networkCommunicator: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var networkCommunicator: EventsCommunicator { get }
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkCommunicator: NetworkManager = NetworkManager()
}
Playground fails with following error:
error: strings.playground:29:7: error: type 'Injector' does not
conform to protocol 'MyAccountCommunicatorProvider' class Injector:
MyAccountCommunicatorProvider, EventsCommunicatorProvider {
^
strings.playground:30:9: note: candidate has non-matching type
'NetworkManager'
var networkCommunicator: NetworkManager = NetworkManager()
^
error: strings.playground:29:7: error: type 'Injector' does not
conform to protocol 'EventsCommunicatorProvider' class Injector:
MyAccountCommunicatorProvider, EventsCommunicatorProvider {
^
strings.playground:30:9: note: candidate has non-matching type
'NetworkManager'
var networkCommunicator: NetworkManager = NetworkManager()
Environment: swift4, XCode9
Why do you need MyAccountCommunicatorProvider and EventsCommunicatorProvider protocols? What if you combine them into one?
protocol CommunicatorProvider {
var networkCommunicator: NetworkManager { get }
}
class Injector: CommunicatorProvider {
var networkCommunicator = NetworkManager()
}
protocol MyAccountCommunicator {
func getAccountData() -> String
}
protocol EventsCommunicator {
func getEvents() -> String
}
protocol MyAccountCommunicatorProvider {
var accountCommunicator: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var eventCommunicator: EventsCommunicator { get }
}
class NetworkManager: MyAccountCommunicator, EventsCommunicator {
func getAccountData() -> String {
return "Accounts"
}
func getEvents() -> String {
return "Events"
}
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkManager = NetworkManager()
var accountCommunicator: MyAccountCommunicator {
return networkManager
}
var eventCommunicator: EventsCommunicator {
return networkManager
}
}
You can use like this :
protocol MyAccountCommunicatorProvider {
var networkCommunicator1: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var networkCommunicator2: EventsCommunicator { get }
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkCommunicator: NetworkManager = NetworkManager()
lazy var networkCommunicator1: MyAccountCommunicator = {
return networkCommunicator
}()
lazy var networkCommunicator2: EventsCommunicator = {
return networkCommunicator
}()
}
Related
I am running into a problem with Swift protocols. I have something like this:
protocol OnboardingPage {
var viewModel: OnboardingPageViewModel! { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
// ---
class FirstNameViewController: UIViewController, OnboardingPage {
var viewModel: FirstNameViewModel!
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class GenderViewController: UIViewController, OnboardingPage {
var viewModel: GenderViewModel!
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
I have a bunch of view controllers that confirm to the OnboardingPage protocol, and they all have a view model that all conform to the OnboardingPageViewModel protocol.
But sadly the code above doesn't work: it says FirstNameViewController doesn’t confirm to the OnboardingPage protocol, since its viewModel property has type FirstNameViewModel instead of OnboardingPageViewModel - even though FirstNameViewModel IS a OnboardingPageViewModel.
How can I solve this?
Use associated type OnboardingPageViewModel type ViewModel
Edited
protocol OnboardingPage {
associatedtype ViewModel: OnboardingPageViewModel
var viewModel: ViewModel { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
class FirstNameViewController: OnboardingPage {
let viewModel: FirstNameViewModel
init(viewModel: FirstNameViewModel) {
self.viewModel = viewModel
}
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class GenderViewController: OnboardingPage {
let viewModel: GenderViewModel
init(viewModel: GenderViewModel) {
self.viewModel = viewModel
}
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
You may solve your pages array issue with deletion of associatedtype:
protocol OnboardingPage {
var viewModel: OnboardingPageViewModel { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class FirstNameViewController: OnboardingPage {
let viewModel: OnboardingPageViewModel
init(viewModel: FirstNameViewModel) {
self.viewModel = viewModel
}
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
class GenderViewController: OnboardingPage {
let viewModel: OnboardingPageViewModel
init(viewModel: GenderViewModel) {
self.viewModel = viewModel
}
}
Usage:
let nameModel = FirstNameViewModel()
let genderModel = GenderViewModel()
let array: [OnboardingPage] = [FirstNameViewController(viewModel: nameModel), GenderViewController(viewModel: genderModel)]
for page in array {
print(page.viewModel.getValue())
}
However, in such manner concrete types of your models will not be available for callers of OnboardingPage conforming classes. You may try to solve it with implementing visitor pattern within OnboardingPage or enhancing its capabilities.
Of course, you can always write let pages: [Any] = [FirstNameViewController(), GenderViewController()] using Cruz example, but it is ugly as it goes, and I strongly discourage you from using Any in your API if possible.
the following code gives me a compile error
'WeakReference' requires that 'ServiceDelegate' be a class type
protocol ServiceDelegate: AnyObject {
func doIt()
}
class SomeClass() {
// compile error occurs in this line
private var observers = [WeakReference<ServiceDelegate>]()
}
WeakReference code:
final class WeakReference<T: AnyObject> {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
How can I fix this error? Delegate should be declared correctly as per this site.
What I have tried so far:
Changing the delegate protocol conformance from AnyObject to
class does not solve the problem.
Try the above code in a clean playground.
You can't have a WeakReference<ServiceDelegate>. ServiceDelegate itself is not an AnyObject, it just requires that anything that conforms to it be an AnyObject.
You would need to make SomeClass generic and use the generic type as the type for the WeakReference:
class SomeClass<T: ServiceDelegate> {
private var observers = [WeakReference<T>]()
}
If the generic on SomeClass is too constricting and you want to be able to have instances of multiple unrelated classes as observers then I would do it by abandoning the generic parameter on WeakReference:
final class WeakServiceDelegate {
private(set) weak var value: ServiceDelegate?
init(value: ServiceDelegate?) {
self.value = value
}
}
class SomeClass {
private var observers = [WeakServiceDelegate]()
}
Alternatively you could make WeakReference conditionally conform to ServiceDelegate:
extension WeakReference: ServiceDelegate where T: ServiceDelegate {
func doIt() {
value?.doIt()
}
}
And then use an array of ServiceDelegate in SomeClass:
class SomeClass {
private var observers = [ServiceDelegate]()
func addObserver<T: ServiceDelegate>(_ observer: T) {
observers.append(WeakReference(value: observer))
}
}
As you see, ServiceDelegate is a protocol, not a class type.
Even if all types which can conform to ServiceDelegate are class types, ServiceDelegate itself is not a class type. It is the fact of the pure Swift protocols currently.
Try #obc, Objective-C protocols are a bit different:
#objc protocol ServiceDelegate {
func doIt()
}
You may want to exclude Objective-C something and to make some pure Swift classes conform to ServiceDelegate, but I cannot find other ways around.
The problem is that WeakReference<ServiceDelegate> is wrong at line
private var observers = [WeakReference<ServiceDelegate>]()
You have to use a concrete class instead of protocol inside <>
You have two possible solutions:
Create a concrete class and use it:
class ServiceClass: ServiceDelegate {
//...
}
private var observers = [WeakReference<ServiceClass>]()
Or use a protocol. I mean this:
final class WeakReference<T: AnyObject> {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
protocol SomeContainer: AnyObject { }
extension WeakReference: SomeContainer { }
and use this way:
private var observers = [SomeContainer]()
Note
Using this way:
class SomeClass<T: ServiceDelegate> {
private var observers = [WeakReference<T>]()
}
You just move the problem to another part of the code.
I had similar problem and ended up keeping generic WeakReference, but removing type constraint:
struct WeakReference<T> {
private weak var storage: AnyObject?
var value: T? {
get { return storage.map { $0 as! T } }
set {
storage = newValue.map { $0 as AnyObject }
}
}
init(value: T?) {
self.value = value
}
}
This works for classes, Objective-C protocols and Swift protocols:
protocol P: class {}
#objc protocol Q {}
class Z: P, Q {}
var z = Z()
var rz = WeakReference<Z>(value: z)
var rp = WeakReference<P>(value: z)
var rq = WeakReference<Q>(value: z)
assert(rz.value === z)
assert(rp.value === z)
assert(rq.value === z)
z = Z()
assert(rz.value === nil)
assert(rp.value === nil)
assert(rq.value === nil)
Unfortunately it compiles for other things too:
protocol R {}
struct S: R {}
var rr = WeakReference<R>(value: S())
print("rr =", rr.value as Any) // nil
var rs = WeakReference<S>(value: S())
print("rs =", rs.value as Any) // nil
In Swift anything can be casted to AnyObject, but for value types that means boxing - new instance is allocated and immediately lost, so it always produces nil.
This can be used to implement an assertion that casting to AnyObject preserves identity:
struct WeakReference<T> {
private weak var storage: AnyObject?
var value: T? {
get { return storage.map { $0 as! T } }
set {
storage = newValue.map {
let asObject = $0 as AnyObject
assert(asObject === $0 as AnyObject)
return asObject
}
}
}
init(value: T?) {
self.value = value
}
}
Alternative approach would be to use https://github.com/wickwirew/Runtime to validate kind of T.self.
create plain protocol
public protocol AnyWeakValue {
var anyValue: Any? { get }
}
inherit associatedtype protocol from AnyWeakValue
public protocol WeakValue: AnyWeakValue {
associatedtype ValueType
var value: ValueType? { get }
}
extension WeakValue {
public var anyValue: Any? { return value }
}
create class Weak inherit WeakValue
open class Weak<Value: AnyObject>: WeakValue {
public init(value: Value?) { self.value = value }
open private(set) weak var value: Value?
}
using example
private var _delegates: [AnyWeakValue] = []
public var delegates: [SomeProtocol] {
return _delegates.compactMap({$0.anyValue as? SomeProtocol})
}
public func register<Delegate>(_ delegate: Delegate) where Delegate: SomeProtocol {
let weak: Weak<Delegate> = Weak.init(value: delegate)
_delegates.append(weak)
}
I am trying to implement generic for MVVM Swift. I have two base class, an protocol for generic class. The special in here is inheritance. I tried for three hour but I can't fix it :(.
protocol ObjectProtocol {
var id: Int { get set }
var name: String { get set }
}
class BaseViewModel<T: ObjectProtocol> {
var objects: [T] = []
init(){
}
}
protocol ListViewControllerType {
associatedtype T: ObjectProtocol
associatedtype ViewModelType: BaseViewModel<T>
var viewModel: ViewModelType! { get set }
func showError(error: String)
}
extension ListViewControllerType {
func showError(error: String) {
print(error)
}
}
class Consult: ObjectProtocol {
var id: Int = 1
var name: String = "Consult"
}
class ConsultViewModel<T: Consult>: BaseViewModel<Consult> {
}
class ConsultViewController: ListViewControllerType {
var viewModel: ConsultViewModel<Consult>!
}
But I get error in var viewModel: ConsultViewModel<Consult>!
This is error: Type 'ConsultViewController' does not conform to protocol 'ListViewControllerType'
Someone have experience with generic and inheritance can help me please.
Thank you so much.
I usually do like this:
class BaseViewModel {
}
class TemplateViewModel<T: ObjectProtocol>: BaseViewModel {
var objects: [T] = []
init(){
}
}
I can't understand the problem with Swift Generic
Here is my snippet of code
protocol StateClient: class {
func update<NewState: State, PrevState: State>(state: NewState, prevState: PrevState)
}
protocol State {
init(client: StateClient)
}
protocol ProfileState: State {
var isActiveState: Bool { get }
var isInactiveState: Bool { get }
func setActiveState()
func setInactiveState()
}
extension State {
func setState<T: State, C: StateClient>(class: T.Type, client: C?) {
guard let client = client else {
return
}
let newState = T(client: client)
client.update(state: newState, prevState: self)
}
}
// - Implementation
struct InactiveState: ProfileState {
private weak var client: StateClient?
var isActiveState: Bool = false
var isInactiveState: Bool = true
init(client: StateClient) {
self.client = client
}
func setActiveState() {
setState(class: ActiveState.self, client: client)
}
func setInactiveState() {
}
}
struct ActiveState: ProfileState {
private weak var client: StateClient?
var isActiveState: Bool = true
var isInactiveState: Bool = false
init(client: StateClient) {
self.client = client
}
func setActiveState() {
}
func setInactiveState() {
}
}
I can't compile the code above. The error :
error: MyPlayground22.playground:38:51: error: in argument type 'StateClient?', 'StateClient' does not conform to expected type 'StateClient'
setState(class: ActiveState.self, client: client)"
What's wrong ?
Any advice would be appreciated)
I have a generic Container struct that is stored as a container of a class that implements a protocol as below:
protocol Protocol {}
class Class : Protocol {}
struct Container<Element> {
var value:Element
init(_ v:Element) { value = v }
}
class Example {
var container : Container<Class>
init() { container = Container(Class()) }
func getContainer() -> Container<Protocol> { return container }
}
However when I pass it I want to pass it as a Container of just the protocol which is generating an error on return container:
Cannot convert return expression of type 'Container<Class>' to return type 'Container<Protocol>'
I know this should be possible as Swift does it with arrays as below:
class ArrayExample {
var arrayContainer : [Class]
init() { arrayContainer = [Class()] }
func getArrayContainer() -> [Protocol] { return arrayContainer }
}
So does anyone have any thoughts on how to implement Container so that the conversion/copy doesn't create the error
It isn't clear what you're actually trying to accomplish, but here's code that compiles and that covers all the goals you've expressed so far:
protocol Protocol {}
class Class : Protocol {
var thing : AnyObject?
}
class Container<Element> {
var value:Element
init(_ v:Element) { value = v }
}
class Example {
var container : Container<Protocol>
init() { container = Container(Class()) }
func getContainer() -> Container<Protocol> { return container }
func doStuff() {
if let v = container.value as? Class { v.thing = "ha" }
}
}