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
Swift - Class Method Which Must Be Overridden by Subclass
How to Benchmark Swift Code Execution
How to Reset/Restart Viewcontroller in Swift
How to Make Nsattributedstring Codable Compliant
Why Is Forwarding Variadic Parameters Invalid
Common Equatable Class on Swift
Error: Bool Is Not Convertible to Void:
Error Handling in Swift Does Not Involve Stack Unwinding. What Does It Mean
Clgeocoder in Swift - Unable to Return String When Using Reversegeocodelocation
How to Make Protocol Associated Type Require Protocol Inheritance and Not Protocol Adoption
Check If Any Property in an Object Is Nil - Swift 3
iOS Swift: Video Thumbnail Error
Custom Bullets from Images on Uitextview or Uilabel Swift
How to Present Different Navigation Title When Large Title Collapse
What Is the Property Block Declaration Equivalent in Swift of the Following Block Property
Convert Bar Chart to a Grouped Bar Chart with Danielgindi/Ios-Charts and Swift
Navigationview Doesn't Display Correctly When Using Tabview in Swiftui