Cannot assign to a property: 'class' is a 'let' constant - swift

I'm trying to update a property on a service that is defined as let subscriber: SubscriberContextProviding in the WatchlistViewModel by simply setting it directly like so:
subscriber.watchlist = watchlist
relevant subscriber definition:
final class Subscriber: SubscriberContextProviding {
var watchlist = [String]()
}
however I get an error saying: Cannot assign to property: 'subscriber' is a 'let' constant
. The subscriber service is declared as a let and is initialised in the client init.
here is the relevant protocol part & init.
protocol SubscriberContextProviding {
var watchlist: [String] { get set }
}
class WatchlistViewModel: NSObject {
let subscriber: SubscriberContextProviding
init(subscriber: SubscriberContextProviding){
self.subscriber = subscriber
super.init()
}
}
however If I change the protocol from the one above to
protocol SubscriberContextProviding {
func set(watchlist: [String])
}
and I simply define the function in the subscriber as
func set(watchlist: [String]){
self.watchlist = watchlist
}
and instead of setting the property directly now using the function like so
subscriber.set(watchlist: watchlist)
works no problem. Why the first approach doesn't work while the latter one does as the result is the same of both approaches?

The "issue" (though it's not an issue, really) is that you haven't restricted SubscriberContextProviding to being a class-bound protocol (with : AnyObject).
let subscriber: SubscriberContextProviding is declaring that your subscriber variable with contain an existential of any object whose type conforms to SubscriberContextProviding. Since that protocol isn't class-bound, it's possible that the concrete value you're dealing with is a value type (tuple, struct or enum), for which mutations are only allowed on mutable var variables. This existential is itself a value type, and abides the same rules of only allowing mutations on var variables.
Thus, you must either:
Declare SubscriberContextProviding as class-bound:
protocol SubscriberContextProviding: AnyObject { ... }
or
Keep your protocol as-is, but make your subscriber variable mutable, to account for the possibility that it contains a value type.

Add this to your protocol:
protocol SubscriberContextProviding: AnyObject{
var watchlist: [String] { get set }
}

Related

Cannot special non-generic type AnyObject

public protocol Subscriber : class {
}
public struct Subscription {
private weak var subscriber:AnyObject<Subscriber>? = nil
}
Why can't I use AnyObject with a protocol for this var?
Swift doesn't allow combining a type and a protocol the way Objective-C does, although you can specify a combination of protocols. Since AnyObject is in fact a protocol, you can accomplish what you want to express above by writing:
public struct Subscription {
private weak var subscriber:protocol<Subscriber, AnyObject>? = nil
}
This requires subscriber to conform to both the Subscriber and the AnyObject protocols.
In your case above, you actually don't need the AnyObject part since you made the Subscriber protocol a class protocol which essentially guarantees that any conforming type is also an AnyObject. So you could just write:
public struct Subscription {
private weak var subscriber:Subscriber? = nil
}
But the protocol<Subscriber, AnyObject> approach would allow your Subscriber protocol to not be restricted to only class types, while making that specific subscriber weak var restricted to class types that implement Subscriber.
To refer to an object that conforms to a protocol, just use the protocol. Swift is not like Objective-C where you need to specify id<SomeProtocol>:
public struct Subscription {
private weak var subscriber: Subscriber? = nil
}
You can also declare your protocol like this if you want to restrict usage to AnyObject, rather than using class:
public protocol Subscriber : AnyObject {
// ...
}

Setting a delegate generates a compile error

I want to use a strategy pattern to register a set of objects that implement a protocol. When I set this up, I get a compile error when trying to set the delegate that is part of the protocol.
For discussion purposes, I have slightly reworked the DiceGame from the Swift eBook's Delegation chapter. The changes of significance are:
protocol DiceGame - declares a delegate
class SnakesAndLadders implements DiceGame (and therefore the protocol and delegate)
class Games holds 3 instances of SnakesAndLadders as
1) a concrete class of SnakesAndLadders
2) a 'let' constant of protocol DiceGame
3) a 'var' variable of protocol DiceGame
We can set the delegate fine if we use the concrete class (snakesAndLadders). However, there is a compile error if we use 'let' to hold it as a protocol (diceGameAsLet) but it compiles if we hold the variable as a 'var' (diceGameAsVar).
It is easy to work around, however, the delegate itself never changes so should be held as a 'let' constant, as it is only the internal property that changes. I must not understand something (possibly subtle but significant) about protocols and how they work and should be used.
class Dice
{
func roll() -> Int
{
return 7 // always win :)
}
}
protocol DiceGame
{
// all DiceGames must work with a DiceGameDelegate
var delegate:DiceGameDelegate? {get set}
var dice: Dice {get}
func play()
}
protocol DiceGameDelegate
{
func gameDidStart( game:DiceGame )
func gameDidEnd( game:DiceGame )
}
class SnakesAndLadders:DiceGame
{
var delegate:DiceGameDelegate?
let dice = Dice()
func play()
{
delegate?.gameDidStart(self)
playGame()
delegate?.gameDidEnd(self)
}
private func playGame()
{
print("Playing the game here...")
}
}
class Games : DiceGameDelegate
{
let snakesAndLadders = SnakesAndLadders()
// hold the protocol, not the class
let diceGameAsLet:DiceGame = SnakesAndLadders()
var diceGameAsVar:DiceGame = SnakesAndLadders()
func setupDelegateAsClass()
{
// can assign the delegate if using the class
snakesAndLadders.delegate = self
}
func setupDelegateAsVar()
{
// if we use 'var' we can assign the delegate
diceGameAsVar.delegate = self
}
func setupDelegateAsLet()
{
// DOES NOT COMPILE - Why?
//
// We are not changing the dice game so want to use 'let', but it won't compile
// we are changing the delegate, which is declared as 'var' inside the protocol
diceGameAsLet.delegate = self
}
// MARK: - DiceGameDelegate
func gameDidStart( game:DiceGame )
{
print("Game Started")
}
func gameDidEnd( game:DiceGame )
{
print("Game Ended")
}
}
DiceGame is a heterogeneous protocol that you're using as a type; Swift will treat this type as a value type, and hence (just as for a structures), changing its mutable properties will mutate also the instance of the protocol type itself.
If you, however, add the : class keyword to the DiceGame protocol, Swift will treat it as a reference type, allowing you to mutate members of instances of it, without mutating the instance itself. Note that this will constraint the protocol as conformable to only by class types.
protocol DiceGame: class { ... }
With the addition of the above, the mutation of immutable diceGameAsLet:s properties will be allowed.
In this context, it's worth mentioning that the : class keyword is usually used to constrain protocols used as delegates (e.g., DiceGameDelegate in your example) as conformable to only by class types. With this additional constraint, the delegates can be used as types to which the delegate owner (e.g. some class) only hold a weak reference, useful in contexts where a strong reference to the delegate could create a retain cycle.
See e.g. the 2nd part of this answer for details.
The issue is that when you store something as a Protocol, even if it is a class, swift considers them to be a value type, instead of the reference type you are expecting them to be. Therefore, no part of it is allowed to be changed. Take a look at this reference for more information.

Statically typed properties in Swift protocols

I'm trying to use Protocol-Oriented Pgrogramming for model layer in my application.
I've started with defining two protocols:
protocol ParseConvertible {
func toParseObject() -> PFObject?
}
protocol HealthKitInitializable {
init?(sample: HKSample)
}
And after implementing first model which conforms to both I've noticed that another model will be basically similar so I wanted to create protocol inheritance with new one:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
var value: AnyObject { get set }
}
A you can see this protocol has one additional thing which is value but I want this value to be type independent... Right now I have models which use Double but who knows what may show up in future. If I leave this with AnyObject I'm sentenced to casting everything I want to use it and if I declare it as Double there's no sense in calling this BasicModel but rather BasicDoubleModel or similar.
Do you have some hints how to achieve this? Or maybe I'm trying to solve this the wrong way?
You probably want to define a protocol with an "associated type",
this is roughly similar to generic types.
From "Associated Types" in the Swift book:
When defining a protocol, it is sometimes useful to declare one or
more associated types as part of the protocol’s definition. An
associated type gives a placeholder name (or alias) to a type that is
used as part of the protocol. The actual type to use for that
associated type is not specified until the protocol is adopted.
Associated types are specified with the typealias keyword.
In your case:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
typealias ValueType
var value: ValueType { get set }
}
Then classes with different types for the value property can
conform to the protocol:
class A : BasicModel {
var value : Int
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
class B : BasicModel {
var value : Double
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
For Swift 2.2/Xcode 7.3 and later, replace typealias in the
protocol definition by associatedtype.

In swift, why can I set a computed property of a polymorphic variable via optional chaining, but not on an unwrapped optional?

I ran into what I think is a strange error in may app. At the bottom of this question is complete code the reproduces what I am seeing in my app, but here is a quick demonstration.
I create two instances of the same class, one is declared as an optional conforming to a protocol the other as an optional of a concrete class
For both I can set the computed property via option chaining ie:
anOptionalInstance?.someComputedProperty = ....
for the concrete version I can set the property by unwrapping the optional
if let anInstance = anOptionalInstance {
anInstance.someComputedProperty = ....
}
For the polymorphic version, I get an error message that says I can't set the property on the instance.
Below is a complete file that reproduces the issue I am seeing.
Can anyone explain what is happening here?
struct MyStruct {
var someMember: String
}
protocol MyProtocol {
var myVar: MyStruct { get set }
}
class MyType: MyProtocol {
var myVar: MyStruct {
get {
return MyStruct(someMember: "some string")
}
set {
println(newValue)
}
}
}
class UsingClass {
var anInstanceOfMyType: MyProtocol?
var anOtherInstanceOfMyType: MyType?
func someMethod() {
anInstanceOfMyType = MyType()
anInstanceOfMyType?.myVar = MyStruct(someMember: "blah")
if let anInstanceOfMyType = anInstanceOfMyType {
// The following line produces this error :Cannot assign to 'myVar' in 'anInstanceOfMyType'
anInstanceOfMyType.myVar = MyStruct(someMember: "blah blah")
}
anOtherInstanceOfMyType = MyType()
anOtherInstanceOfMyType?.myVar = MyStruct(someMember: "blah")
if let anOtherInstanceOfMyType = anOtherInstanceOfMyType {
anOtherInstanceOfMyType.myVar = MyStruct(someMember: "blah blah")
}
}
}
The problem does happen because you are trying to change the property of the constant anInstanceOfMyType which type is MyProtocol.
1. Why anInstanceOfMyType is a constant?
At the first line of UsingClass, anInstanceOfMyType is actually declared as var. However with the Conditional Unwrapping a constant with name anInstanceOfMyType is created, and you are trying to change a property of that constant
2. Ok but anInstanceOfMyType references an instance of a class, so I should be able to change its properties even if it's a constant
Since anInstanceOfMyType has MyProtocol as type, it could contain a struct or a reference an instance of a class.
So the compiler does apply the safer approach and avoid you to change its properties.
Solution
Limit protocol adoption to class types (and not structures or enumerations) by adding the class keyword to a protocol’s inheritance list. The class keyword must always appear first in a protocol’s inheritance list, before any inherited protocols:
protocol MyProtocol: class {
var myVar: MyStruct { get set }
}
or
If MyProtocol is updated to extend AnyObject
protocol MyProtocol : AnyObject {
var myVar: MyStruct { get set }
}
then becomes clear that anInstanceOfMyType must refer an instance of a class, in this case your code does compile.

Specify a settable property/variable in a protocol

I would like my protocol to declare that there is a read/write property available. I have attempted it, but this does not work:
protocol EdibleThing {
var eaten: Bool { get set }
}
class Pickle: EdibleThing { var eaten = false }
class RusticGrapefruit: EdibleThing { var eaten = false }
class Jar {
let contents: [EdibleThing] = [Pickle(), RusticGrapefruit()]
var nextItem: EdibleThing {
return contents.last ?? Pickle() // Lazy pickle generation technology
}
func eat() {
let food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
}
What am I doing wrong? I think I've declared that the protocol has a get/set var called eaten, so why can't I set it?
The protocol might be implemented by either classes and structs - that prevents you from changing the internal status of an instance of a class or struct implementing that protocol using an immutable variable.
To fix the problem you have to either declare the food variable as mutable:
func eat() {
var food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
or declare the EdibleThing protocol to be implementable by classes only:
protocol EdibleThing : class {
var eaten: Bool { get set }
}
Note that this happens because food is a variable of EdibleThing type - the compiler doesn't know if the actual instance is a value or reference type, so it raises an error. If you make it a variable of a class type, like this:
let food: Pickle = nextItem as! Pickle
the compiler knows without any ambiguity that it's a reference type, and in that case it allows the assignment. But I guess that breaks your app logic... so consider it just as an example
You're mutating food.
Replace let food = nextItem with var food = nextItem
The problem is that you can't mutate a property on a value type defined by let.
Even though both of RusticGrapefruit and Pickle are class implementations (reference types), the protocol could be assigned to a value type like a struct. The compiler detects a potential problem and stops us.
Two solutions:
Change let to var (in my case, this would mean changing a lot of code that refers to objects of this type. Also, I like the semantic value and possible compiler optimizations from let)
Declare the protocol as only valid for classes: protocol EdibleThing: class { }