Swift Struct with Lazy, private property conforming to Protocol
Because accessing the lazy data
variable mutates AStruct
, any access to it must be marked as also mutating the struct. So you need to write:
struct AStruct : Example {
// ...
var var1: String {
// must declare get explicitly, and make it mutating:
mutating get {
// act of looking at data mutates AStruct (by possibly initializing data)
return self.data.var1
}
}
var varArray:[String] {
mutating get {
return self.data.varArray
}
}
}
However, you'll find now that Swift complains you aren't conforming to Example
, because its get var1
isn't marked as mutating. So you'd have to change it to match:
protocol Example {
var var1:String { mutating get }
var varArray:[String] { mutating get }
}
Swift protocol with lazy property - Cannot use mutating getter on immutable value: '$0' is immutable
As you can tell from the error message, $0
is immutable, so you can't use a mutating member on it.
Therefore, you can't iterate over the bars
directly. What you can do is iterating through its indices
, because we know that bars[$0]
is mutable:
print(bars.indices.map { bars[$0].footype }.reduce(0.0, +))
Swift: implement a protocol variable as a lazy var?
Citing the Language Guide - Properties - Lazy Stored Properties [emphasis mine]:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used.
I.e., the value is mutated upon first usage. Since foo
has been blueprinted in the Foo
protocol as get
, implicitly nonmutating get
, the value type Bar
does not fulfil this promise with its lazy
property foo
, a property with a mutating
getter.
Changing Bar
to a reference type will allow it to fulfil the Foo
blueprint (as mutating a property of a reference type doesn't mutate the type instance itself):
protocol Foo {
var foo: String { get }
}
class Bar: Foo {
lazy var foo: String = "Hello World"
}
Alternative, specify in the blueprint of the foo
property of Foo
that it has a mutating
getter.
protocol Foo {
var foo: String { mutating get }
}
struct Bar: Foo {
lazy var foo: String = "Hello World"
}
See the following Q&A for some additional details of the mutating
/nonmutating
specifiers for getters and setters:
- Swift mutable set in property
Can a struct have lazy properties [instantiation] in Swift?
Yes a struct can have a lazy property. Consider this example:
class Stuff {
var stuff: Int
init(value: Int) {
print("Stuff created with value \(value)")
stuff = value
}
}
struct HasLazy {
lazy var object = Stuff(value: 1)
var object2 = Stuff(value: 2)
}
func testIt() {
print("in testIt")
var haslazy = HasLazy()
print("done")
haslazy.object.stuff = 17
print("\(haslazy.object.stuff)")
print("final")
}
testIt()
Output:
in testIt
Stuff created with value 2
done
Stuff created with value 1
17
final
Notice that the property marked lazy
is not initialized until after "done"
prints when the property is first accessed.
See it in action here, and then try it without the lazy
keyword.
Swift protocol with properties that are not always used
Unlike in Objective-C, you cannot define optional protocol requirements in pure Swift. Types that conform to protocols must adopt all the requirements specified.
One potential way of allowing for optional property requirements is defining them as optionals, with a default implementation of a computed property that just returns nil
.
protocol Nameable {
var name : String? { get }
var fullName : String? { get }
var nickname : String? { get }
}
extension Nameable {
var name : String? { return nil }
var fullName : String? { return nil }
var nickname : String? { return nil }
}
struct Person : Nameable {
// Person now has the option not to implement the protocol requirements,
// as they have default implementations that return nil
// What's cool is you can implement the optional typed property requirements with
// non-optional properties – as this doesn't break the contract with the protocol.
var name : String
}
let p = Person(name: "Allan")
print(p.name) // Allan
However the downside to this approach is that you potentially pollute conforming types with properties that they don't implement (fullName
& nickName
in this case).
Therefore if it makes no logical sense for a type to have these properties (say you wanted to conform City
to Nameable
– but cities don't (really) have nicknames), you shouldn't be conforming it to Nameable
.
A much more flexible solution, as you say, would be to define multiple protocols in order to define these requirements. That way, types can choose exactly what requirements they want to implement.
protocol Nameable {
var name : String { get }
}
protocol FullNameable {
var fullName : String { get }
}
protocol NickNameable {
// Even types that conform to NickNameable may have instances without nicknames.
var nickname : String? { get }
}
// A City only needs a name, not a fullname or nickname
struct City : Nameable {
var name : String
}
let london = City(name: "London")
// Person can have a name, full-name or nickname
struct Person : Nameable, FullNameable, NickNameable {
var name : String
var fullName: String
var nickname: String?
}
let allan = Person(name: "Allan", fullName: "Allan Doe", nickname: "Al")
You could even use protocol composition in order to define a typealias
to represent all three of these protocols for convenience, for example:
typealias CombinedNameable = Nameable & FullNameable & NickNameable
struct Person : CombinedNameable {
var name : String
var fullName: String
var nickname: String?
}
How to properly make a lazy derived property on a mutating struct in Swift?
Using an embedded class gets around the limitations on mutating a struct. This lets you use a by‐value type that does not run expensive computations until they are needed, but still remembers the result afterward.
The example Number
struct below computes and remembers its square property in a way that behaves just like you describe. The math itself is ridiculously inefficient, but it is a simple way to illustrate the solution.
struct Number {
// Store a cache in a nested class.
// The struct only contains a reference to the class, not the class itself,
// so the struct cannot prevent the class from mutating.
private class Cache {
var square: Int?
var multiples: [Int: Int] = [:]
}
private var cache = Cache()
// Empty the cache whenever the struct mutates.
var value: Int {
willSet {
cache = Cache()
}
}
// Prevent Swift from generating an unwanted default initializer.
// (i.e. init(cache: Number.Cache, value: Int))
init(value: Int) {
self.value = value
}
var square: Int {
// If the computed variable has been cached...
if let result = cache.square {
// ...return it.
print("I’m glad I don’t have to do that again.")
return result
} else {
// Otherwise perform the expensive calculation...
print("This is taking forever!")
var result = 0
for var i = 1; i <= value; ++i {
result += value
}
// ...store the result to the cache...
cache.square = result
// ...and return it.
return result
}
}
// A more complex example that caches the varying results
// of performing an expensive operation on an input parameter.
func multiple(coefficient: Int) -> Int {
if let result = cache.multiples[coefficient] {
return result
} else {
var result = 0
for var i = 1; i <= coefficient; ++i {
result += value
}
cache.multiples[coefficient] = result
return result
}
}
}
And this is how it performs:
// The expensive calculation only happens once...
var number = Number(value: 1000)
let a = number.square // “This is taking forever!”
let b = number.square // “I’m glad I don’t have to do that again.”
let c = number.square // “I’m glad I don’t have to do that again.”
// Unless there has been a mutation since last time.
number.value = 10000
let d = number.square // “This is taking forever!”
let e = number.square // “I’m glad I don’t have to do that again.”
// The cache even persists across copies...
var anotherNumber = number
let f = anotherNumber.square // “I’m glad I don’t have to do that again.”
// ... until they mutate.
anotherNumber.value = 100
let g = anotherNumber.square // “This is taking forever!”
As a more realistic example, I have used this technique on date structs to make sure the non‐trivial computations for converting between calendar systems are run as little as possible.
Protocol with Empty Init
You can use as following:
struct MyAppUser: User {
// default init coming from protocol
init() {
self.firstname = ""
self.lastname = ""
}
// you can use below init if you want
init(firstName: String, lastName: String) {
self.firstname = firstName
self.lastname = lastName
}
// coming from protocol
var firstname: String
var lastname: String
}
// user 1
var user1 = MyAppUser()
user1.firstname = "Ashis"
user1.lastname = "laha"
print(user1)
// user 2
let user2 = MyAppUser(firstName: "Kunal", lastName: "Pradhan")
print(user2)
Output:
MyAppUser(firstname: "Ashis", lastname: "laha")
MyAppUser(firstname: "Kunal", lastname: "Pradhan")
Why I can't use let in protocol in Swift?
"A var in a protocol with only get isn't just a let?" No. A let
indicates a constant. But that is not the case here. Consider the following:
protocol SomeProtocol {
var someProperty: String { get }
}
class SomeClass : SomeProtocol {
var someProperty: String = ""
func cla () {
someProperty = "asd"
}
}
let someInstance = SomeClass()
print(someInstance.someProperty) // outputs ""
someInstance.cla()
print(someInstance.someProperty) // outputs "asd"
The protocol specifies what the conforming class shows to the outside - some property of type String
named someProperty
which you can at least get.
If the protocol specifies { get }
your class can choose to conform via let someProperty: String = ""
but it can similarly choose to conform via the above code. If on the other hand the protocol specifies { get set }
you cannot use let
in the implementation but have to make it set-able as well.
A protocol simply cannot define that a value has to be constant - neither should it, that is an implementation detail that has to be taken care (or decided about) by the class / struct that implements it.
Related Topics
Get the Type of Anyobject Dynamically in Swift
How to Convert 4 Bytes to a Swift Float
Swiftui Overlay Blocking List Scroll Events
How to Switch an Xcode Project to Use Swift Version 1.2 in the Xcode 7 Beta
Change the Font of a Datepicker
Big O of Accessing a String with an Index in Swift 3.0
How to Compare Cgpoints in Swift
How to Make a Function Complete Before Calling Others in an Ibaction
Supportedinterfaceorientationsforwindow in Swift 2.0
Changing Tab Bar Color (Swift)
Set Uitextfield Placeholder Color Programmatically
Nsinvocationoperation' Is Unavailable in Xcode 6.1
Classes in Swift Files Inside Folder References Not Seen by Xcode 10's Compiler
Swift Ui: Center Text into Circle
Tap Gesture Not Working as Expected When Added to Uiview in Collectionview Cell