Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Struct with Lazy, private property conforming to Protocol

First, I have a protocol that defines only a few, readonly properties, ex:

protocol Example {
  var var1:String { get }
  var varArray:[String] { get }
}

I then want to create a struct that conforms to that protocol. The problem I'm running into, is that I have two conflicting requirements:

  1. The properties need to be lazily generated.
  2. The properties are related and need to be generated together.

I can't seem to find a way to do this. The closest I've come is something like this:

struct AStruct : Example {
  private lazy var data:(var1:String, varArray:[String]) = {
    var stringValue:String = ""
    var stringArray:[String] = []
    //Generate data
    return (stringValue, stringArray)
  }()

  var var1:String {
    return self.data.var1
  }

  var varArray:[String] {
    return self.data.varArray
  }
}

The problem is, I'm getting the error: Immutable value of type 'AStruct' only has mutating members named 'data'.

Does anyone know of a way I might be able to accomplish my goal? Technically, the data variable is mutable but will never change. I can't use let with lazy so I can't specify that the value will never change once it's been generated. I need the values to be generated though, because the struct is created on the main thread, but the values will be generated on a background thread by another process altogether.

Update

So it was pointed out to me that I can make the getters mutating in both the protocol and the struct. That works, except I now have the problem that I cannot use this struct in any other struct (which I am). So in the end, I've pushed off the problem into another struct, which I do not want to be mutable.

Ex:

struct Container {
  let astruct:AStruct
  let callback:() -> ()
}

I cannot access the variables in AStruct from Container because Container is immutable and the member variables of AStruct are mutating. Trying to access them gives me the same error message I spoke of earlier.

Changing the container to use var instead of let yields the same error:

struct Container {
  var astruct:AStruct
  let callback:() -> ()
}

If I set up a function in the processing class that receives a Container to process:

func processContainer(cont:Container){
  self.doSomething(cont.astruct.var1)
}

I get the same error: Immutable value of type 'AStruct' only has mutating members names 'sql'.

like image 772
Aaron Hayman Avatar asked Jan 28 '15 21:01

Aaron Hayman


People also ask

What are lazy properties in Swift language?

Lazy Properties in Swift Swift language allows you to create several different types of properties, including computed, property observers and even lazy properties. In this article, we will learn how lazy properties can provide performance benefits for time consuming calculations. Implementation

What is a struct in Swift?

In Swift, a struct is used to store variables of different data types. For example, Suppose we want to store the name and age of a person. We can create two variables: name and age and store value. However, suppose we want to store the same information of multiple people.

Can a swift property have an instance variable?

A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly. This approach avoids confusion about how the value is accessed in different contexts and simplifies the property’s declaration into a single, definitive statement.

How do you define a static variable in Swift?

In C and Objective-C, you define static constants and variables associated with a type as global static variables. In Swift, however, type properties are written as part of the type’s definition, within the type’s outer curly braces, and each type property is explicitly scoped to the type it supports.


2 Answers

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 }
}
like image 195
Airspeed Velocity Avatar answered Oct 02 '22 02:10

Airspeed Velocity


So I want to detail out the solution I ended up following. As it turns out, I don't think what I want is currently possible in Swift. Once you start down the mutating get road, it ends up cascading into too many areas (all container structs need to be mutating, etc). In the end, this kind of ruined the whole reason I wanted to use structs in the first place.

Perhaps down the road Apple will add a lazy let var = .... This would ensure immutability by guaranteeing the lazy variable is only ever set once... just not immediately.

So my solution was simply to forego structs altogether and use classes instead. I keep the classes functionally immutable, so I still retain that. Quite literally, all I had to do was change struct to class and now the lazy construction works perfectly and all my problems go away... except I'm using a class.

All that said, @AirspeedVelocity has a correct solution, even if untenable for my needs, so I'll be accepting his solution. I'm just leaving this here so others can understand how I overcame the problem... use classes.

like image 26
Aaron Hayman Avatar answered Oct 02 '22 03:10

Aaron Hayman