I'm confused when using get set in protocols. Using only get works fine, but the set part doesnt'.
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
...
Then when I use it in the viewController
self.mainViewModel.localDoor = $0
But this gives me the error
Cannot assign to property: 'mainViewModel' is a get-only property
How do I set it up properly?
EDIT
Initiation of the viewModel is done with factory based dependency injection
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
It is totally depends on how you create object for mainViewModel.
Let's create some cases with your code:
import UIKit
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
Case 1
Here we are creating an object and assigning object through getter as a closure.
So, here mainViewModel has only getter not setter i.e. it'a get-only property.
var mainViewModel: MainViewModelProtocol { MainViewModel.instance }
mainViewModel.localDoor = "assign some thing" // Error: Cannot assign to property: 'mainViewModel' is a get-only property
Case 2
Here we are directly assigning object to mainViewModelOther. So, this will be a normal property and you can make changes in properties of model.
var mainViewModelOther: MainViewModelProtocol = MainViewModel.instance
mainViewModelOther.localDoor = "assign some thing"
Case 3
You can also create a class that will hold your model object, and created another object of your class. You can make changes in properties of model.
class MyClass {
var mainViewModel: MainViewModelProtocol = MainViewModel.instance
}
let myClassObj = MyClass()
myClassObj.mainViewModel.localDoor = "assign some thing"
TL;DR
Mark your MainViewModelProtocol as being class-only (i.e. protocol MainViewModelProtocol: class { ... }) to solve the issue.
The long answer
To understand why marking your MainViewModelProtocol as class-only fixes the problem, we need to take couple steps back and look at how structs and classes are stored internally.
Case 1: MainViewModelProtocol is a reference-type (i.e. class)
First, let's consider the case where MainViewModel is a class: Classes are reference-types, which means that after you retrieve the your view model through the mainViewModel property, you have a pointer to the same view model that is stored inside your view controller. Modifying the referenced type will also modify the view model of the view itself (since they both point to the same object). As an example
/* ... */
class MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
modifies the view model that's shared between the local variable viewModel and the view controller. This is exactly what you want.
Case 2: MainViewModelProtocol is a value type (i.e. struct)
Now let's consider if the MainViewModel was a struct: structs are value-types, so retrieving the view model through the mainViewModel computed property essentially clones the view model. Now you might modify the retrieved view model as much as you like locally, but there is no way assign it back to your view controller
/* ... */
struct MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
just modifies the local copy of the view model stored in the viewModel variable. There is no way to assign the local variable back to myViewController.
Conclusion
I hope this illustrates why your pattern only works with reference-types and not value types.
Now the Swift compiler needs to be conservative and consider both cases since it doesn't know if all types conforming to MainViewModelProtocol will be classes or structs (consider public protocols vended as a library to which library-users can conform). If you add the class-constraint to the protocol, you tell the compiler that using the pattern from Case 1 is totally fine – just grab a shared instance and modify it – and that there is no need for a setter to modify the view model.
No need to mark MainViewModelProtocol as class only, when the compiler says :
Cannot assign to property: 'mainViewModel' is a get-only property
it's actually complaining about your view controller implementation. I assume mainViewModel is a computed property so you can't assign it.
I managed to reproduce your error with the following playground :
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
final class FakeVC {
var mainViewModel: MainViewModelProtocol {
MainViewModel.instance
}
}
var viewController = FakeVC()
viewController.mainViewModel.localDoor = "foo" // Cannot assign to property: 'mainViewModel' is a get-only property
I got rid of the error by changing FakeVC implementation to :
final class FakeVC {
var mainViewModel: MainViewModelProtocol = MainViewModel()
}
Related
Given this context where I have a class that implements a protocol's property where that property is prefixed with the property wrapper #Published.
protocol Manageable {
var foo: String { get }
}
class Manager: Manageable {
#Published var foo: String
}
I found myself in the following situation where I can't call the published property as $ using the protocol.
let manager: Manageable = Manager()
manager.$foo // <== Error
Because we can't declare the #Published in the protocol I found myself in the position where I have to cast it to the class that implements it but is not what I want.
What I did to solve it were declaring a new publisher type property that returns the $foo property in its implementation as follow.
protocol Manageable {
var foo: String { get }
var fooPublisher: Published<String>.Publisher { get }
}
class Manager: Manageable {
#Published var foo: String
var fooPublisher: Published<String>.Publisher {
return $foo
}
}
So now I call the publisher.
let manager: Manageable = Manager()
manager.fooPublisher // <== Works
I wonder if this is a proper way to solve this.
Have you guys found another way to do it ?
I'm trying to learn protocols and associatedtypes. I have couple of protocols which declare associatedtypes, starting with:
protocol MasterViewModel {
associatedtype Item: AWMediaItem
...
}
AWMediaItem is another protocol
protocol AWMediaItem {
var name: String { get }
var source: AWMediaSource { get }
}
And AWAlbum is yet another protocol which inherits from AWMediaItem
protocol AWAlbum: AWMediaItem {
var albumName: String { get }
...
}
For some reason, in a class implementing MasterViewModel protocol, I cannot set the AWAlbum to be the Item.
final class AlbumsMasterViewModel: MasterViewModel {
typealias Item = AWAlbum // Error
...
}
The warning I get is
Possibly intended match 'AlbumsMasterViewModel.Item' (aka 'AWAlbum') does not conform to 'AWMediaItem'
If I understand correctly, all AWAlbum's will implement AWMediaItem so why is this not working?
I think you meant to write
final class AlbumsMasterViewModel<Item: AWAlbum>: MasterViewModel {
}
I assume that when you write :
typealias Item = AWAlbum // Error
you want AlbumsMasterViewModel's item to conform to your AWAlbum protocol but you just create a typelias meaning that Item is just an alias for AWAlbum .
If you want to use a type alias you need a concrete type conforming to AWMediaItem, not a protocol inheriting from it. eg :
class ConcreteAlbum: AWAlbum {
var albumName: String
var name: String
var source: AWMediaSource
...
}
final class AlbumsMasterViewModel: MasterViewModel {
typealias Item = ConcreteAlbum // No Error
}
Edit
if you want to use AlbumsMasterViewModel with multiple Item types you can also declare it that way :
final class AlbumsMasterViewModel<Item: AWMediaItem>: MasterViewModel {
}
protocol BasePresenterProtocol : class {}
protocol DashboardPresenterProtocol : BasePresenterProtocol {}
final class DashboardPresenter {
weak var view: DashboardPresenterProtocol?
init() {
self.view = DashboardViewController()
}
func test() {
print("Hello")
}
}
extension DashboardPresenter: DashboardViewProtocol { }
protocol BaseViewProtocol : class {
weak var view: BasePresenterProtocol? { get set }
}
protocol DashboardViewProtocol : BaseViewProtocol {
}
class DashboardViewController {
}
extension DashboardViewController: DashboardPresenterProtocol { }
In the above code, I get an error at following line
extension DashboardPresenter: DashboardViewProtocol { }
that, DashboardPresenter doesn't confirm to protocol DashboardViewProtocol, but I have declared weak var view: DashboardPresenterProtocol? in DashboardPresenter . Although I have declared
Why am I getting this error ? Please let me know what I am doing wrong in this code.
You cannot implement a read-write property requirement of type BasePresenterProtocol? with a property of type DashboardPresenterProtocol?.
Consider what would happen if this were possible, and you upcast an instance of DashboardPresenter to DashboardViewProtocol. You would be able to assign anything that conforms to BasePresenterProtocol to a property of type DashboardPresenterProtocol? – which would be illegal.
For this reason, a read-write property requirement has to be invariant (although it's worth noting that a readable-only property requirement should be able to be covariant – but this currently isn't supported).
Why declare readonly property in protocol if we can set value trough class or struct? I can not understand usage of this.
In "The Swift Programming Book" version 2.0
“If the protocol only requires a property to be gettable, the requirement can be satisfied by any kind of property, and it is valid for the property to be also settable if this is useful for your own code.”
So that it's not settable from outside the class/struct. Imagine your API returned some instance of a protocol that has a get and set property (in your protocol), then anyone getting this instance would be able to set the value!
Also get and set properties can't be constants:
protocol RWProt {
var value : Int { get set }
}
// Error: Type 'Value' does not conform to protocol 'RWProt'
struct Value : RWProt {
let value = 0
}
This however works:
protocol Read {
var value : Int { get }
}
struct Value : Read {
var value = 0
mutating func change() {
value++
}
}
The protocol only needs the value to be gettable, so get protocols properties are not get only but rather get or set
Okay, here is another example:
import Foundation
public protocol ExternalInterface {
var value : Int { get }
}
private struct PrivateStuff : ExternalInterface {
var value = 0
mutating func doSomePrivateChangingStuff() {
value = Int(arc4random())
}
}
public func getInterfaceToPrivateStuff() -> ExternalInterface {
var stuff = PrivateStuff()
stuff.doSomePrivateChangingStuff()
return stuff
}
// In another file:
let interfaceToSomethingICantChange = getInterfaceToPrivateStuff()
// error: cannot assign to property: 'value' is a get-only property
interfaceToSomethingICantChange.value = 0
class ArcaneCardVC: UIViewController {
var currentCard: ArcaneCardView?
}
class PostVC: ArcaneCardVC {
override var currentCard: PostCard?
// <===== This is what I want to do but cant
}
class ArcaneCardView: UIView {
}
class PostCard: ArcaneCardView {
}
Here is the error I get:
Cannot override mutable property 'currentCard' of type 'ArcaneCardView?' with covariant type 'PostCard?'
The other solution is explicitly doing this in code everytime I use currentCard:
var card = currentCard as! PostCard
When you override a variable, you can't change it's type. Why not? Well, suppose that you are able to do that, then the following scenario would be possible:
var A: PostVC = PostVC() // some initialization
var B: ArcaneCardVC = A // this is a valid state since `PostVC` is a subclass of `ArcaneCardVC`
What should be the type of B.currentCard? Hmm, this is a complicated question. You can answer that its type should be PostCard. Ok, lets add other classes to the party:
class OtherCard: ArcaneCardView {
}
class OtherVC: ArcaneCardVC {
override var currentCard: OtherCard?
}
Considerer now the following code:
var A: ArcaneCardVC = PostVC()
var B: ArcaneCardVC = OtherVC()
A.currentCard = B.currentCard // something will crash here!!!
To avoid this kind of behavior, you can't change the type of a property when you are subclassing.
The correct way to do it is the way you're doing with currentCard as! PostCard.
Another option would be to use a property getter like
// inside PostVC
// Note the camel case on the 'C' makes it a different variable that the super class
var CurrentCard: PostCard {
get { return self.currentCard as! PostCard }
}
Then you'd use self.CurrentCard instead of self.currentCard as! PostCard