I'm really confused about the purpose of getter and setter methods in Swift as someone coming from Java world
so in Swift there is something called computed property which lets you create a dynamic variable and you can provide setters and getters for a specific property just like this
var property: Int {
get {
return anotherProperty - 4
}
set {
print("property now has a new value which is \(newValue)")
}
}
the setter gets executed after the property gets assigned with a new value ex:
property = 100
what i don't understand is that setters in general are called when we want to set a new value not after the value is set, that is their purpose and that is why it is called setter(it sets the property with a new value).
am i getting it all wrong? or if i am right then what is the purpose of setters in Swift and why are they useful at all if it's not for setting the value of a property?
Related
I would like to hide some property setters and initializers on my Swift model objects. These are reference data that the server provides, and under no circumstances should they be created or modified by the application. This is simple enough in Swift.
However, there is application in my project (a separate target) that needs to break this rule. It is a tool I use to populate the data in bulk, so of course needs to be able to initialize new model objects and set their properties.
What are my options for accomplishing this? I would rather not use a completely new project since it will mean a lot of code duplication. Is there some language-level way to keep this mutability hidden from one application but available to another?
If you declare a property with the let keyword. It can then only be set in the init of the type.
You can also declare a private setter to make the property readonly from the caller of the type but read/write inside the type
struct Foo {
private(set) var bar: Bool = true
func toggle() {
bar.toggle()
}
}
var foo = Foo()
let barState = foo.bar // This works
foo.toggle() // This works too
foo.bar.toggle() // This will make a compile time error
I declared a variable to be settable and gettable. In its getter, I have some logic, but not in setter. I decided to use computed property:
var product: Product? {
// setter
set(newProduct) {
// COMPILER WARNING: Attemping to modify 'product' within its own setter
product = newProduct
}
// getter
get {
let price = SOME_EQUATION
return Product(price)
}
}
The issue is in setter, compiler is warning me Attemping to modify 'product' within its own setter. But I do want to set the newProduct to the product instance variable. What is the correct way to do that?
I assume that SOME_EQUATION is using the value from the product, right? If not, then the setter is not needed at all.
Also, there's a side effect to the way you wrote the code: every time you get the value of the product property, you'll get a new instance.
But answering your question: for computed properties, there's no storage in the instance. So what you might want to do is to create a private property and store value into it instead:
private var _product: Product?
var product: Product? {
// setter
set(newProduct) {
// COMPILER WARNING: Attemping to modify 'product' within its own setter
_product = newProduct
}
// getter
get {
let price = SOME_EQUATION(_product)
return Product(price)
}
}
In your example you will always return Product(SOME_EQUATION). If this equation do not depend on the class containing the product property maybe consider moving this as a factory method in the Product.
Note that setting value in its' set is recursion.
To achieve real computed property:
var product: Product? {
return Product(SOME_EQUATION)
}
Note that let product = Product(SOME_EQUATION) is actually better than computed property if it is suitable in your case, because this equation will be executed only once.
Edit:
Computed properties are not used for what you are describing. Having no logic in the setter, and not using directly the value that you have set is wrong.
As I have described above, you need to create factory method in your Product class as:
static func create(for someEquation: YourType) -> Product {
return Product(someEquation)
}
And in your class you use it as:
Product.create(for: SOME_EQUATION)
More info on computed properties: you use them when you already have dependancies in your class, and you want to extract some point of your data for convenience.
If you depend on some parameter, and not dependancy, you would better make function, taking parameter!
Let's started with the code snippet:
St Foo {
var proA: Int = 0 { // needs initialization
willSet {
print("about to set proA to \(newValue) from \(proA)")
}
didSet {
print("already set proA to \(proA) from \(oldValue)")
}
}
var ProB: Int { // do not needs initialization
return 1
}
}
let foo = Foo()
foo.proA = 23
print(foo.ProB)
Here are some of my personal understandings about the the stored and computed property:
a: Property with only the observer (willSet and didSet) is not a computed property but a stored property (ex. the proA property in the code above).
b: Computed property must not has initialization (See the comments of the code above).
c: setter is kind of equal to the property observer, the property observer is just the setter + the observer to of the before and after mutating.
Questions:
1. I wonder what makes a property a computed property? Is it correct that as long as the property has a getter and return it is a computed property?
2. Are all my understandings (a, b & c) correct? If not, would be nice of you to point out.
3. Why is it not allowed to initialize an computed property? (Please see the figure below) And when I do so the compiler gives out the warning Cannot call value of none-function type "int" What's the meaning of this error?
Thanks a lot.
First, this is about variables, not properties. Any variable can be a computed variable. A property is just one way to use a variable.
I think on the whole you are making a big mistake in putting a stored variable with setter observers side by side with a computed variable. They are unrelated!
Think of a computed variable as something that looks and acts like a variable when you use it — you get and (maybe) set it — but is in fact a function (or a pair of functions). It is just a compact way of calling a function. That's all it is.
A stored variable with observers, on the other hand, is just a stored variable that also has some observers.
Okay, on to your questions:
I wonder what makes a property a computed property? Is is correct that as long as the property has a getter and return it is a computed property?
Yes. It's a computed variable because you declared it using the syntax that makes it a computed variable (with the curly braces).
Are all my understandings (a, b & c) correct? If not would be nice of you to point out
Yes. I think your "c" is quite insightful: a computed variable does not need a setter observer because it has (gasp!) a setter!
Why is it not allowed to initialize an computed property? (Please see the figure below) And when I do so the compiler gives out the warning Cannot call value of none-function type "int" What's the meaning of this error?
There is no sense in which a computed variable "has" a value — it is computed! it's just some functions! — so it makes no sense to assign it an "initial" value.
A stored property is a property of which the property value is stored together with the instance of the class or struct. The value may be changed, but the property can also be a constant. Thus a stored property can be as simple as:
var proA: Int
let proB: Int
var proC: Int = 0
Computed properties do not store a value. Thus you cannot assign a value to a computed property. A Computed property should have a getter that returns a value. I a broad term, you can think of a computed property as a property that returns the value of a function.
Example of Computed Property
var proA: Int {
return proB * proC
}
With regards to your questions:
Computed Property is therefor a property that do not store a value, and contains a get to return the 'computed' value of the property.
a is correct, b computed properties should not have initialization, c if you mean willSet and didSet. Yes they are like observers for when the property's value will change and did change respectively
Since the value of a computed property is not stored and will never be used, the compiler forbids it.
Hope this helps a bit.
I wonder what makes a property a computed property? Is is correct that as long as the property has a getter and return it is a computed property?
If you define get { } inside the property declaration, it makes that property to a computed property.
And it cannot have initial value as when you access the property, it will always call get{} function declared in property.
Are all my understandings (a, b & c) correct? If not would be nice of you to point out
a is correct
b is wrong.
You can not set initial value for computed property.
Because as I explained in question 1, it will always return result of get{} when you need access to the property.
c : 50% right
setter , it can also be used as to store newValue into another private variable and you can do some additional observing logic. So to observe value changes on stored property, you use willSet and didSet
You can define observing logic on computed property (which has getter and setter) on set{} declaration. But main purpose of set {} is to store the value to another variable or for example UserDefaults.
Why is it not allowed to initialize an computed property? (Please see the figure below) And when I do so the compiler gives out the warning Cannot call value of none-function type "int" What's the meaning of this error?
Same answer
Your code makes compiler to be confused
When you set initial value on the property on declaration, the compiler tries to understand it as stored property. But you also defined get{} for this property, and it means it is computed property and should always return 22 when you access the property. So you should remove one of two.
a. Yes,a property with only observer is a stored property not a computed property.Beacuase property observer tracks the value of a property whose value has initialised previously & it's now changing ,that's a stored property. It's not applicable for a computed property since it has no predefined value
b. computed property is a property whose value depends on other variables, we should declare only those properties as computed property , who needs to be computed using value of another variables ,so it's value cannot be initialised in advance.
for e.g. - If we have 2 variables a & b. we need their addition value , so a variable named 'sum' is used , then sum will be declared as a computed property & its get{} block will return (a+b) that's sum of a & b & the value of sum variable.Then in this case we can't initialise property 'sum'
in advance because it will be computed using a & b.
c. Setter is not an observer it sets value of another variable or performs some actions related to other variables whereas a property observer tracks changes in value of its associated variable itself. for e.g. it's meaningless to use a property observer for variable 'sum' as described in point b .
I'm using a custom getter like so
var currentShowLiked: Bool {
get {
return [some condition met] ? true : false
}
set {
self.currentShowLiked = newValue
}
}
and it works fine. However, I would expect to be able to set true or false value back to my variable, but Swift forced my to implement a setter, which does not produce any warnings, but at runtime if I'm to change value of my variable, app crashes with no apparent reason or sane explanation pointing to setter line with EXC_BAD_ACCESS (code=2...) and a message in console warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
Why do I do?
You are in recursive context.
You can't use currentShowLiked in the currentShowLiked property body as below:
// Recursive, Infinite loop
self.currentShowLiked = newValue
To fix this:
private var _currentShowLiked : Bool
var currentShowLiked: Bool {
get {
return [ some condition met on _currentShowLiked ] ? true : false
}
set {
self._currentShowLiked = newValue
}
}
A Swift property that defines a custom getter and / or setter operation is a computed property which does not actually stores a value. The custom setter can be used to modify the internal state of other properties.
See also: "The Swift Programming Language" page 180
You can not set value in its setter because you are creating a recursion -method calling itself because:
self.currentShowLiked = newValue
will call set method you defined which will go and go. If you are overriding setter and getter you probably want to have some backing store property which will be private.
Moreover you defined a getter which base on some condition so anyway you are not using the value you set on this property. I think you do not need this set
The error here is caused by a misunderstanding.
When you declare a
var a:Type
A member value a is allocated inside the object, and accessors and mutators are automatically created to allow you to read and write that variable.
However, when you declare
var a:Type { get { ... } set { ... } }
No member value is allocated - you have indicated that when the value is accessed (read) or mutated (written) from a user of the object, your code will do all necessary action to respond to it. If you ultimately want the value to be stored, you will need to store it to an actual value, (which will need a different name).
Since you are invoking the mutator of the object inside the mutator of the object, you have set up an infinite loop, which causes your program to crash due to stack overflow (all function call memory is used to store the record of the function calling itself over and over again).
The code you have above will crash because it causes an infinite loop - your setter for currentShowLiked sets currentShowLiked to the new value, so then that calls the setter again, and so on.
You don't have to implement a setter, but you then don't use get - the syntax is like this:
var currentShowLiked: Bool {
return [some condition met]
}
I am not sure is this is correct behaviour or if its unintended. I have setup StealthFighter so that it returns a class type computed property variable called ammunition.
func globalTests() {
println("globalTests")
println("AMMUNITION: \(StealthFighter.ammunition)")
var myStealthFighter = StealthFighter()
println("MISSILES: \(myStealthFighter.missiles)")
println("AMMUNITION: \(myStealthFighter.ammunition)") // ERROR
}
class StealthFighter {
class var ammunition:Int {
return 500;
}
var missiles: Int = 5
}
When directly accessing the class StealthFighter this works fine and returns 500 as expected. But if I create and instance myStealthFighter and then try and access the class property on the instance I get the error: 'StealthFighter' does not have a member named 'ammunition' I can't find any mention of this, I am assuming from this that class properties are accessible only via the class? and not on any instances created from it? I just want to make sure I am understanding this correctly ...
EDIT:
So I have probably worded the type variable name wrong as it should probably be maxAmmunition to signify that StealthFighters can only take 500 rounds. I can see the point, if you want the maxAmmunition for the class then you ask the class.
As #Kreiri and #0x7fffffff points out it does seem that you can ask the instance what the class ammunition (or maxAmmunition) is by using dynamicType.
println("CLASS - AMMUNITION: \(StealthFighter.ammunition)")
var myStealthFighter = StealthFighter()
println("INSTA - AMMUNITION: \(myStealthFighter.dynamicType.ammunition)")
.
// OUTPUT
// CLASS - AMMUNITION: 500
// INSTA - AMMUNITION: 500
Your assumption is correct. Type variables are only meant to be accessed directly from the class. If you want to get at them from an instance, you can do so by accessing the dynamicType property on your instance, like so.
let theFighter = StealthFighter()
let missiles = theFighter.dynamicType.missiles
println(missiles)
However, I don't think that this is the correct approach for you to be taking here. Assuming that you want to have one class "StealthFighter", and possibly multiple instances of that class, each with the ability to have its own number of missiles independent of the others, you should probably make this an instance variable by simply ditching the class keyword.
dynamicType allows access instance’s runtime type as a value, so accessing class property from instance would look like this:
var myStealthFighter = StealthFighter()
myStealthFighter.dynamicType.ammunition
Works in playground, at least.
These properties are known as Type properties in swift. It should be called on its type ie class name, not on instance. Type properties holds same value across all the instances of the class just like static constant in C.
Querying and Setting Type Properties
Type properties are queried and set with dot syntax, just like instance properties. However, type properties are queried and set on the type, not on an instance of that type
Excerpt from : swift programming language
Swift 4:
var myStealthFighter = StealthFighter()
type(of: myStealthFighter).ammunition
Yes. This is a correct behaviour. These Type Properties can only be accessed over the Type and are not available on the instance itself.
In the Swift Book from Apple it is described in the section "Type Properties" (Page 205).
Swift Type Properties
“Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time"