Why Can't I Change Variables in a Protocol Extension Where Self Is a Class

Why can't I change variables in a protocol extension where self is a class?

Your example doesn't compile because MyProtocol isn't class-bound, and as such can have mutating requirements and extension members. This includes property setters, which are by default mutating. Such members are free to re-assign a completely new value to self, meaning that the compiler needs to ensure they're called on mutable variables.

For example, consider:

public protocol MyProtocol {
init()
var i: Int { get set } // implicitly `{ get mutating set }`
}

extension MyProtocol {
var i: Int {
get { return 0 }
// implicitly `mutating set`
set { self = type(of: self).init() } // assign a completely new instance to `self`.
}
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
func a() {
i = 0 // error: Cannot assign to property: 'self' is immutable
}
}

final class C : MyProtocol2 {
init() {}
}

let c = C()
c.a()

If this were legal, calling c.a() would re-assign a completely new instance of C to the variable c. But c is immutable, therefore the code is not well formed.

Making MyProtocol class bound (i.e protocol MyProtocol : AnyObject or the deprecated spelling protocol MyProtocol : class) works because now the compiler knows that only classes can conform to MyProtocol. Therefore it imposes reference semantics by forbidding mutating requirements and extension members and therefore prevents any mutations of self.

Another option at your disposal is to mark the setter for the requirement i as being nonmutating – therefore meaning that it can only be satisfied by a non-mutating setter. This makes your code once again well-formed:

public protocol MyProtocol {
init()
var i: Int { get nonmutating set }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
func a() {
i = 0 // legal
}
}

How to define variable that can be set and get in extension of protocol

The simplest way i think is define the variable in the protocol with the getter and setter. Then in your conform object you should declare the variable to conformance.
An Example:

protocol AbstractObject {

var maxProductCount: Int { get set }
}

struct ConformObject: AbstractObject {

var maxProductCount: Int
}

So now you can use your variable in your default implementations

extension AbstractObject {

mutating func addOne() -> Int {
self.maxProductCount += 1
return self.maxProductCount
}
}

Swift - Protocol extensions - Property default values

It seems you want to add a stored property to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.

I can show you a couple of alternatives.

Subclassing (Object Oriented Programming)

The easiest way (as probably you already imagine) is using classes instead of structs.

class IdentifiableBase {
var id = 0
var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name = "test"
print(a.name) // test

Cons: In this case your A class needs to inherit from IdentifiableBase and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.

Components (Protocol Oriented Programming)

This technique is pretty popular in game development

struct IdentifiableComponent {
var id = 0
var name = "default"
}

protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}

Now you can make your type conform to Identifiable simply writing

struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}

Test

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

Swift protocol default values are not changable


var type1: AssetViewAttribures = Crypto(name: "name", logo: URL(string: "https://pixabay.com/de/illustrations/online-maus-web-internet-weltweit-523234/")!, symbol: "symbol", avgPrice: "123", precision: 2)

type1.avgPrice

This would call the getter declared in the protocol extension, which just returns "". This is because Crypto.avgPrice has no relation to the avgPrice declared in the protocol extension. You can't "override" a member in an extension, because extensions are dispatched statically. The compiler sees that test is of type AssetViewAttributes, finds the default getter you have declared in the extension, and that's what it will call.

To fix this, you need to add avgPrice as a requirement of the protocol:

protocol AssetViewAttributes {
...
var avgPrice: String { get }
}

This causes Swift to find avgPrice declared in the protocol, and dispatches it dynamically. If the implementing class happens to implement avgPrice, that implementation will be called. If not, then the default implementation is called.

How to resolve collision between protocol and class fields types?

Property names and types declared in a protocol must exactly be matched by the conforming classes.

So you cannot resolve the error without changing the property type in either the protocol or the conforming type. You could also rename one of the properties and add the matching property to the conforming type as a new field.

So either do:

protocol Named {
var name: String { get }
}

class Person {
var name: String

init(_ name:String) {
self.name = name
}
}


extension Person: Named {

}

Or

protocol Named {
var name: String { get }
}

class Person {
var _name: String!
}


extension Person: Named {
var name: String {
return _name
}
}

As @user28434 pointed out, there's a(n ugly) workaround. You can create a wrapper protocol that matches the optionality of the Person class, make that protocol inherit from the original protocol, declare the non-optional variable in an extension on the new protocol and make Person conform to the new protocol instead of the original Named.

protocol Named {
var name: String { get }
}

class Person {
var name: String!
}

protocol Namedd: Named {
var name: String! { get }
}

extension Namedd {
var name: String {
return name!
}
}

extension Person: Namedd {

}

Swift Protocol Extensions overriding

The short answer is that protocol extensions don't do class polymorphism. This makes a certain sense, because a protocol can be adopted by a struct or enum, and because we wouldn't want the mere adoption of a protocol to introduce dynamic dispatch where it isn't necessary.

Thus, in getColor(), the color instance variable (which may be more accurately written as self.color) doesn't mean what you think it does, because you are thinking class-polymorphically and the protocol is not. So this works:

let colorB = B().color // is "Red color" - OK

...because you are asking a class to resolve color, but this doesn't do what you expect:

let b = B().getColor() // is "Default color" BUT I want it to be "Red color"

...because the getColor method is defined entirely in a protocol extension. You can fix the problem by redefining getColor in B:

class B: A, RedColor {
func getColor() -> String {
return self.color
}
}

Now the class's getColor is called, and it has a polymorphic idea of what self is.

Unexpected Cannot assign to property 'self' is immutable compile time error IN CLASS

It's a bug, but not the bug you might think (and the error message is no help at all). It's a known bug caused by the fact that the keyboardAppearance property of the UITextInputTraits protocol is an optional property (meaning that it might not be implemented). This is an Objective C feature, not a Swift feature, and the bug is that such properties are not directly settable in Swift, even when they are marked {get set}.

To see this, let's emulate the same schema for ourselves:

@objc public protocol P {
@objc optional var keyboardAppearance : NSString {get set}
}

open class C : NSObject {
private var wrapped: P
public init(wrapped: P) {
self.wrapped = wrapped
super.init()
}
open var keyboardAppearance : NSString {
get {
wrapped.keyboardAppearance ?? "" as NSString
}
set {
wrapped.keyboardAppearance = newValue
}
}
}

We get the same error. But if you delete the keyword optional, the error goes away. This proves that the optional is what causes the issue. But you cannot delete optional in your situation, because that protocol doesn't belong to you.

The workaround is the same as for the original bug — use a key path:

let kp = \C.wrapped.keyboardAppearance
self[keyPath:kp] = newValue

Protocol Extension Initializer

You have to provide a valid chain of init for creating an instance of a class and that limits your options for initializers in protocols.

Since your protocol can't be certain to cover all members of the class that uses it, any initializer you declare in your protocol will need to delegate initialization of the "unknown" members of the class to another initializer provided by the class itself.

I adjusted your example to illustrate this using a basic init() as the delegation initializer for the protocol.

As you can see, this requires that your class implement initial values for all members when init() is called. In this case I did that by providing default values in each member's declaration. And, since there isn't always an actual initial value for some members, I changed them to auto-unwrap optionals.

And to make thinks more interesting, your class cannot delegate initialization to a protocol supplied initializer unless it does so through a convenience initializer.

I wonder if all these restrictions are worth the trouble. I suspect you're trying to use a protocol because you need a bunch of common variables to be initialized consistently between classes that implement the protocol. Perhaps using a delegate class would provide a less convoluted solution than protocols (just a thought).

protocol Thing:AnyObject
{
var color:UIColor! { get set }
init()
}

extension Thing
{
init(color:UIColor)
{
self.init()
self.color = color
}
}

class NamedThing:Thing
{
var name:String! = nil
var color:UIColor! = nil

required init() {}

convenience init(name:String,color:UIColor)
{
self.init(color:color)
self.name = name
}
}


Related Topics



Leave a reply



Submit