Swift refuses to call a setter defined in a protocol extension. This is in Xcode 10.2 beta 4.
// Freestanding test case
protocol HasTally {}
class ClassWithTally: HasTally {}
extension HasTally {
var tally: Int {
get { return 42 }
set { print("Setting tally") }
}
}
ClassWithTally().tally = 42 // Error: Cannot assign to property: function call returns immutable value
This is a simplified test case. I first encountered the problem when trying to use Kingfisher 5 with Swift 5 in Xcode 10.2 beta 4. In that context, the failing code was (essentially):
// As seen in the wild
import Kingfisher
extension UIImageView {
func shouldWork() {
kf.indicatorType = .activity
}
}
However, the type and protocol hierarchy behind the live problem is a bit more complex.
Because set is implicitly mutating in a protocol extension (when not constrained to AnyObject) or value type, this means that you could reassign self in the setter. Therefore, the compiler cannot allow you to call a mutating setter when self is immutable. You can solve this problem by writing nonmutating set.
The reason that you don't need to do this if you define the setter in the class (or a class-constrained protocol extension) is that, inside a class (or a class-constrained protocol extension), the compiler stops you from reassigning self in the setter.
Check out the difference:
protocol HasTally {
var tally: Int { get set }
init()
}
final class ClassWithTally: HasTally {
required init() {}
}
extension HasTally {
var tally: Int {
get { return 42 }
set { self = Self(); print("Setting tally") }
}
}
extension ClassWithTally {
func shouldWork() {
tally = 42 // compiler error is here, not in the setter, because reassigning self is allowed in a protocol extension setter
}
}
protocol HasTally {
var tally: Int { get set }
init()
}
final class ClassWithTally: HasTally {
required init() {}
}
extension ClassWithTally {
var tally: Int {
get { return 42 }
set { self = ClassWithTally(); print("Setting tally") } // error is here, because reassigning 'self' isn't allowed
}
}
extension ClassWithTally {
func shouldWork() {
tally = 42
}
}
protocol HasTally: AnyObject {
var tally: Int { get set }
init()
}
final class ClassWithTally: HasTally {
required init() {}
}
extension HasTally {
var tally: Int {
get { return 42 }
set { self = Self(); print("Setting tally") } // error is also here
}
}
extension ClassWithTally {
func shouldWork() {
tally = 42
}
}