Swift Struct with Lazy, Private Property Conforming to Protocol

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



Leave a reply



Submit