Computed Read-Only Property VS Function in Swift

Computed read-only property vs function in Swift

It seems to me that it's mostly a matter of style: I strongly prefer using properties for just that: properties; meaning simple values that you can get and/or set. I use functions (or methods) when actual work is being done. Maybe something has to be computed or read from disk or from a database: In this case I use a function, even when only a simple value is returned. That way I can easily see whether a call is cheap (properties) or possibly expensive (functions).

We will probably get more clarity when Apple publishes some Swift coding conventions.

Swift function vs lazy var vs computed property - difference?

  • lazy vars are actually stored properties, so you can't put it in extensions or anywhere stored properties are not allowed.
  • The getter for computed properties is run every time you refer to that property. This can be significant especially if the getter is time-consuming or has side-effects to other parts of the code.
  • The getter for lazy vars are only run when the property is first referred to and never again.
  • lazy vars are variables. You can mutate them.
  • Computed properties can optionally have a setter, so sometimes they are read-only.
  • Using a function like that is very similar to a read only computed property. You just have to add () when getting its value.

difference between a computed property and setting a function to a variable in Swift

The code in the computed property gets executed every time you reference that variable. The code in the property initialized by a closure is only executed once, during initialization.

Read-Only properties

Something like this? (as suggested by @vacawama in the comments)

struct Triangle {
let edgeA: Int
let edgeB: Int
let edgeC: Int

var isEquilateral: Bool {
return (edgeA, edgeB) == (edgeB, edgeC)
}
}

Let's test it

let triangle = Triangle(edgeA: 5, edgeB: 5, edgeC: 5)
triangle.isEquilateral // true

or

let triangle = Triangle(edgeA: 2, edgeB: 2, edgeC: 1)
triangle.isEquilateral // false

What's the point of READ-only variables when you have LET?

Let me try to sum up what the other answers are saying while also adding missing information that I think is critical in order to understand this.

Properties

Properties are simply values that are associated with an object and may be queried in a trivial amount of time without the need (or ability) for parameters like methods have.

Stored Properties

When you create a stored property, whether with let or var, the value assigned at any given point in time will be stored in memory, which is why it is called a stored property.

var name = "Matt"

For variables using var, the value is stored in memory in a way that makes it mutable (editable). You can reassign the value at will and it will replace the previous value stored in memory.

let name = "Matt"

For constants using let, the value is also stored in memory, but in such a way that it may not be changed after the first time assigning to it.

Computed Properties

Computed properties are not stored in memory. As ganzogo says in the comments, computed properties act similarly to methods, but do not take parameters. When deciding when to use a computed property or a function with no parameters, the Swift API Design Guidelines recommend using a computed property when it will simply create or fetch, and then return the value, provided that this takes a trivial amount of time.

var fullName: String {
return firstName + lastName
}

Here, we assume that firstName and lastName are already properties on the object. There is no sense of initialization with this property because it is not stored anywhere. It is fetched on demand every time. That is why there is no sense to doing anything like the following:

var dogName : String {
return "Buster"
}

This has no benefit over a stored property except that no memory will be used in storing the String "Buster".

In fact, this is a simplified version of computed properties. You will notice that the Swift Language Guide describes the use of both get and set in a computed property. set allows you to update the state of other variables when one sets a computed variable. For example:

var stored: Int

var computed: Int {

get {
return stored + 5
}

set {
stored = newValue - 5
}

}

Some useful applications of this were pointed out by Rajan's answer, for example getting and setting volume from width, height, and depth.

A read-only computed var is just a computed var which specifies only a getter, in which case the get keyword and brackets are not required.

Read-Only for Access Control

When developing modules such as frameworks, it is often useful to have a variable only be modifiable from within that object or framework and have it be read-only to the public.

private var modifiableItem: String

public var item: String {
return modifiableItem
}

The idea here is that modifiableItem should only be mutable from within the object that defined it. The private keyword ensures that it is only accessible within the scope of the object that created it and making it a var ensures that it may be modified. The public var item, then, is a computed variable that is exposed to the public that enables anyone to read, but not mutate the variable.

As Hamish notes in the comments, this is more concisely expressible by using private(set):

public private(set) var item: String

This is probably the best way to go about it, but the previous code (using a private stored property and public computed one) demonstrates the effect.

Is It Inefficient To Access A Static Property Through Computed Properties In Swift?

As with most optimization questions, it depends on your precise code and the version of the compiler, and also we don't have to guess, we can check.

By "inefficient" I'm going to assume you mean "fails to inline the accessor call."

The TL;DR is: In almost all cases the optimizer will inline this either way. There are corner cases where the accessor version is not inlined, for example if the caller is at the top-level (not inside a function) and the class is non-final. (I don't know why that's a corner-case; it may be an optimizer bug.)

I'm neutral on whether this is a good design. I'm fine with it (and occasionally use this pattern myself). But I certainly wouldn't avoid it out of performance concerns. (In cases where one extra function call would be a problem, you're going to need to hand-optimize anyway.)

The details

As with most optimization/performance questions, it will depend on your exact code and the version of the compiler. As I said, there are some corner cases where this doesn't get optimized. I tested with Swift 5.5.2.

First, I created a test program:

// Avoid the complexity around calling print()
// while ensuring that the variable is not optimized away
@inline(never)
func handle(_ x: Int) {
print(x)
}

// Stick it in a function to avoid the special handling of
// global variables
func f() {
let c = OwnerClass()

let x = OwnerClass.constant1
handle(x)
let y = c.constant1
handle(y)
}

// Make sure to call the function so it's not optimized away
f()

Then I checked it with several version of OwnerClass (I use 12345678 to make it easier to find in the output):

// Class
class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}

// Final class
final class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}

// Struct
struct OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}

// Instance constant
class OwnerClass {
static let constant1 = 12345678
let constant1:Int = OwnerClass.contant1
}

The only one that ever had trouble (for example, when I didn't wrap it all in a function), was the non-final class with an accessor.

To see what the optimizer does, I compiled with swiftc -emit-sil -O x.swift. In all cases, this is what f() compiles to:

// f()
sil hidden @$s1x1fyyF : $@convention(thin) () -> () {
bb0:
%0 = integer_literal $Builtin.Int64, 12345678 // user: %1
%1 = struct $Int (%0 : $Builtin.Int64) // users: %6, %5, %4, %2
debug_value %1 : $Int, let, name "x" // id: %2
// function_ref handle(_:)
%3 = function_ref @$s1x6handleyySiF : $@convention(thin) (Int) -> () // users: %6, %4
%4 = apply %3(%1) : $@convention(thin) (Int) -> ()
debug_value %1 : $Int, let, name "y" // id: %5
%6 = apply %3(%1) : $@convention(thin) (Int) -> ()
%7 = tuple () // user: %8
return %7 : $() // id: %8
} // end sil function '$s1x1fyyF'

The important thing to note is that the constant 12345678 is inlined into the function as %0 (wrapped into %1), and then it's used twice in %4 and %6 to call handle(). No calls are made to the accessor. OwnerClass isn't even referenced (the creation of c is optimized away).

Advantage of computed properties (gettable ones only) vs. stored properties

Computed Properties

var sayGoodMorningToUserComputed: String {
return greeting + username
}

sayGoodMorningToUserComputed acts just like a function. If a change has been made to greeting or username, then sayGoodMorningToUserComputed will return an up-to-date result that will be the concatenation of the current values.

You would want to use this if you want to ensure your returned value is computed off the latest values of its dependencies (greeting and username).

In the case that both dependencies are final, then it's very likely that the compiler would optimise this computed property into a stored property, because it knows the dependencies can't change

Stored properties

var sayGoodMorningToUserStored = greeting + username

sayGoodMorningToUserStored is just a variable, with nothing special going on. However, it's only set once, whenever the containing scope is initialized. It's computed once, stored and remains constant until it is overwritten by an external source. As such, if greeting or username changes, there will be no effect on sayGoodMorningToUserStored, because it's been computed from the old values, and stored.

You would want to use this if you want to improve performance by caching the result of a computation whose dependencies are constant.

Define a read-only property in Swift

You can use a Computed Property which (like a method) can be overridden.

class Parent: UIView {
var itemCount: Int { return 0 }
}

class Child: Parent {
override var itemCount: Int { return 1 }
}

Update (as reply to the comment below)

This is how you declared and override a function

class Parent: UIView {
func doSomething() { print("Hello") }
}

class Child: Parent {
override func doSomething() { print("Hello world!") }
}

Whats is better in swift: a function returning a variable or just a getter variable

Does either method have any advantage over the other?

Not internally, no. A computed property is a function, so there is no difference in implementation under the hood.



Related Topics



Leave a reply



Submit