Using Getters and Setters to modify values w/o Subclassing in Swift - swift

Let's say I have a class, and when I set its property, I want it to append that property with a file type like .fileType:
class File {
var fileName: String {
get {
return self.fileName
}
set {
self.fileName = fileName + ".fileType"
}
}
}
Which I try to use like this:
let newFile = File()
newFile.fileName = "My File"
Unfortunately, the variable never sets:
I have two possible workarounds.
Option 1: Observe Value After Set
class File {
var fileName: String = "" {
didSet {
self.fileName += ".fileType"
}
}
}
let file = File()
file.fileName = "SomeName" // SomeName.fileType
But with this, I must wait until the value is already set before I can modify it. For this particular example, it doesn't make too much difference; however, I'd like to be able to avoid this.
Option 2: Subclass
This solution is based on the example here. Search for 'speedLimitedCar'
class File {
var fileName = ""
}
class SubFile: File {
override var fileName: String {
get {
return super.fileName
}
set {
super.fileName = newValue + ".fileType"
}
}
}
let subfile = SubFile()
subfile.fileName = "Hello" // Hello.fileType
Question
I could also just create a secondary property to store the value and access that in the fileName's getter/setter, but is there a way to avoid all that and modify a property directly within its getter / setter?

If you use set and get then the property must be computed. There is no actual storage for it.
This code defines a var to hold the data and a computed var to handle the access. This is similar to what you had in Objective-C (except that in Objective-C you could "hide" the actual variable by making it private or, more recently, having it synthesized an never mentioned in the header).
class File {
// this stores the actual data, do not access it directly, consider it private
var theFileName: String = ""
// this is the real interface
var fileName: String {
get {
return self.theFileName
}
set(name) {
self.theFileName = name + ".fileType"
}
}
}
You can also write the set like this:
set {
self.theFileName = newValue + ".fileType"
}
where newValue is the default name if you omit the argument declaration for set.
But what you probably want to do is what you already did (and rejected for unknown reasons):
var fileName: String = "" {
didSet {
self.fileName += ".fileType"
}
}
This is the correct way.
Note that "I must wait until the value is already set before I can modify it." is not true. It looks like that, but the compiler can very well optimize the code (and probably will).

Related

Variable 'xxx' was never mutated; in derived class

I'm posting my first message here, I've a logical question about swift language. For your information, I'm quite new in swift language, I'm use to code in c++ and it's a bit hard for me to have an objective point of view on how to do things right (in an elegant way), if you have some advices, pls feel free to do your suggestions.
I'm doing a homemade encapsulation using the following superclass :
class MultiLevel_encapsulation {
var separator = "";
var datas:[String:String] = [:]
func wrap() -> String{
var out:String = ""
var i = 0
for (key, data) in datas{
if i==0{
out += key + separator + data
}
else{
out += separator + key + separator + data
}
i+=1
}
return out
}
func unwrap(content:String){
let split = content.components(separatedBy: separator)
var i = 1
while(i < split.count){
datas[split[i-1]] = split[i]
i += 2
}
}
func getAttributesNames() -> [String]{
var out:[String] = []
for (key, _) in datas{
out.append(key)
}
return out
}
func getValue(name:String) -> String? {
return datas[name];
}
func setValue(name:String, value:String){
datas[name] = value;
}
}
and I want to create some subclasses including the superclass, I just change the separator depending of the subclass name :
class Level5_encapsulation: MultiLevel_encapsulation{
init(message:String) {
super.init()
separator = "&&LEVEL5&&"
unwrap(content:message)
}
override init() {
super.init()
separator = "&&LEVEL5&&"
}
}
So after it I just need to create the subclass as a var in my program, add values and wrap it to have an encapsulated string :
var l5message = Level5_encapsulation()
l5message.setValue(name: #anyTitle#, value: #anyValue#)
var output = l5message.wrap() // String with encapsulated message
Do you think it 's the right way to do it or is there a better way for that ?
My main question is about this compiler warning :
Variable 'l5message' was never mutated; consider changing to 'let' constant
I changed it for a let and it works.
So there is something I don't understand : Why can I change proprieties in the superclass as if the inherited subclass is declared as constant ? Where is the storage of the superclass and how does it works ?
In Swift classes and structs behave a bit differently than in C++. var and let prevent changes to the actual value, and since the variable type that you're using is a class the variable holds a reference, and not the actual data (Like Level5_encapsulation *l5message).
Since you're not mutating the value of the variable (A reference), the compiler raises a warning.

How can a operate with value of setter in structs?

I have this code of swift struct with getter and setter.
How that value of newValue got inside this setter? I don't know how it was defined.
struct Program {
var allResults : [String] = []
var items: Int {
get {
return allResults.count
}
set {
let result = String(newValue * 100) // what's that newValue, how did it get there?
allResults.append(result)
}
}
It is called Shorthand Setter Declaration.
Cited from Swift 3 book:
If a computed property’s setter does not define a name for the new
value to be set, a default name of newValue is used.
If you would like to have a better readable format, you could use this:
...
set (newItems) { //add your own variable named as you like
let result = String(newItems * 100)
allResults.append(result)
}
...

Getters and Setters in Swift - Does it make sense to use WillSet and DidSet instead?

I was doing some research about the reasons we should use Get and Set for our properties.
I've noticed 3 main reasons for it
When you want to do/check something before you actually set the
property
When you want to have a property that you can only Get from it
(maybe for security purposes I guess? ), or give it different access
levels.
Hiding the internal representation of the property while exposing a
property using an alternative representation. (which for me doesn't
make a lot of sense since i can access it on the wrong place using
the Set function anyways)
The code below is a example of how you would implement Get and Set for properties in Swift, taking advantage of those 3 points I mentioned:
class Test
{
private var _testSet:String!
private var _testGetOnly:String
var testSet:String{
get{
return _testSet
}
set{
_testSet = newValue + "you forgot this string"
}
}
var testGetOnly:String!{
get{
return _testGetOnly
}
}
init(testSet:String, testGetOnly:String)
{
_testSet = testSet
_testGetOnly = testGetOnly
}
}
But this other example below also take advantage of those points mentioned but instead of using another computed property to return the private property value I just use the willSet and didSet observers
class Test
{
var testGet:String {
willSet{
fatalError("Operation not allowed")
}
}
var testWillSet:String!{
didSet{
self.testWillSet = self.testWillSet + "you forgot this string"
}
}
init(testGet:String, testWillSet:String)
{
self.testGet = testGet
self.testWillSet = testWillSet
}
}
So I'm curious to know what are the ADVANTAGES and DISADVANTAGES of each implementation.
Thanks in advance
Your question boils down to compile time vs. run time error. To address your 3 questions:
Yes, willCheck is your only option here
Readonly properties fall into 2 types: (a) those whose value derive from other properties, for example, their sum; and (b) those that you want to be able to change by yourself, but not by the users. The first type truly have no setter; the second type has a public getter and a private setter. The compiler can help you check for that and the program will not compile. If you throw a fatalError in didSet you get a runtime error and your application will crash.
There can be state objects that you don't want the user to freely mess with, and yes, you can completely hide those from the users.
Your code first example was too verbose in defining the backing variables - you don't need to do that. To illustrate these points:
class Test
{
// 1. Validate the new value
var mustBeginWithA: String = "A word" {
willSet {
if !newValue.hasPrefix("A") {
fatalError("This property must begin with the letter A")
}
}
}
// 2. A readonly property
var x: Int = 1
var y: Int = 2
var total: Int {
get { return x + y }
}
private(set) var greeting: String = "Hello world"
func changeGreeting() {
self.greeting = "Goodbye world" // Even for private property, you may still
// want to set it, just not allowing the user
// to do so
}
// 3. Hide implementation detail
private var person = ["firstName": "", "lastName": ""]
var firstName: String {
get { return person["firstName"]! }
set { person["firstName"] = newValue }
}
var lastName: String {
get { return person["lastName"]! }
set { person["lastName"] = newValue }
}
var fullName: String {
get { return self.firstName + " " + self.lastName }
set {
let components = newValue.componentsSeparatedByString(" ")
self.firstName = components[0]
self.lastName = components[1]
}
}
}
Usage:
let t = Test()
t.mustBeginWithA = "Bee" // runtime error
t.total = 30 // Won't compile
t.greeting = "Goodbye world" // Won't compile. The compiler does the check for you
// instead of a crash at run time
t.changeGreeting() // OK, greeting now changed to "Goodbye world"
t.firstName = "John" // Users have no idea that they are actually changing
t.lastName = "Smith" // a key in the dictionary and there's no way for them
// to access that dictionary
t.fullName = "Bart Simpsons" // You do not want the user to change the full name
// without making a corresponding change in the
// firstName and lastName. With a custome setter, you
// can update both firstName and lastName to maintain
// consistency
A note about private in Swift 2 vs. Swift 3: if you try this in a Swift 2 playground, you will find t.greeting = "Goodbye world" works just fine. This is because Swift 2 has a strange access level specifier: private means "only accessible within the current file". Separate the class definition and the sample code into different files and Xcode will complain. In Swift 3, that was changed to fileprivate which is both clearer and save the private keyword for something more similar to to Java and .NET

Swift protocol settable property through a read-only property [duplicate]

This question already has an answer here:
Swift: Failed to assign value to a property of protocol?
(1 answer)
Closed 6 years ago.
Can someone please tell me why Swift has to call the setter of a property when it's only being used to access an object (a protocol) in order to set one of its properties? This first example shows the error I get if I don't declare the indirect object as settable:
protocol AProtocol {
var name: String { get set }
}
class AnImplementation: AProtocol {
var name = ""
}
class AParent {
var test = AnImplementation()
}
class AChild {
var parent: AParent!
var test: AProtocol {
get { return parent.test }
// Note: Not settable
}
}
var parent = AParent()
var child = AChild()
child.parent = parent
child.test.name = "Hello world!" // Error: Cannot assign to property : 'test' is a get-only property
print(child.test.name)
If I give it a setter, it compiles and works but it calls the setter:
protocol AProtocol {
var name: String { get set }
}
class AnImplementation: AProtocol {
var name = ""
}
class AParent {
var test = AnImplementation()
}
class AChild {
var parent: AParent!
var test: AProtocol {
get { return parent.test }
set(newTest) { print("Shouldn't be here!") }
}
}
var parent = AParent()
var child = AChild()
child.parent = parent
child.test.name = "Hello world!"
print(child.test.name)
Output is:
Shouldn't be here!
Hello world!
I'm not sure what I'm not understanding here. I assume I can just give it an empty setter, but I'd like to understand the reason for it.
Any information is much appreciated!
Change your protocol declaration to this:
protocol AProtocol:class {
var name: String { get set }
}
Otherwise, it is taken by default as a value type. Changing a value type's property replaces the value type instance (as shown by the setter observer). And you can't do that if the reference is a let reference.
This is probably caused by the fact that the compiler doesn't know whether AChild.test is a class or a value type. With classes there is no problem but with value types assigning to name would also create an assignment to test (value-copy behavior). Marking APProtocol as class protocol will fix the problem.
To expand, when the compiler is not sure whether test is a value or a class type, it will use the following rewrite of child.test.name = "Hello world!":
var tmp = child.test
tmp.test = "Hello world!"
child.test = tmp
because that will work for both class and value types.

Swift computed property to return copy of underlying array

I have a model class written in Objective-C that I'm converting to Swift. It contains an NSMutableArray internally, but the method signature for the getter, as well as the actual return value, are NSArray. When called, it creates an immutable copy to return.
Essentially, I want callers to be able to iterate/inspect the container, but not modify it. I have this test snippet:
class Container {
internal var myItems = [String]()
func sayHello() {
"I have: \(myItems)"
}
}
let cont = Container()
cont.myItems.append("Neat") // ["Neat"]
cont.sayHello() // This causes sayHello() to print: "I have: [Neat]"
var isThisACopy = cont.myItems
isThisACopy.append("Huh") // ["Neat", "Huh"]
cont.sayHello() // This ALSO causes sayHello() to print: "I have: [Neat]"
I've been trying to find a way to override the getter for myItems so that it returns an immutable copy, but can't seem to determine how.
Attempt #1
This produces a compiler error: Function produces expected type '_ArrayBuffer<(String)>'; did you mean to call it with '()'?
internal var myItems = [String]() {
var copy = [String]()
for item in ... { // What to use in the ...?
copy.append(item)
}
return copy
}
Attempt #2
This also produces a compiler error, because I'm (understandably) redefining the generated getter Invalid redeclaration of 'myItems()':
internal func myItems() -> [String] {
var copy = [String]()
for item in myItems {
copy.append(item)
}
return copy
}
Try this:
class Container {
private var _myItems: [String] = ["hello"]
internal var myItems: [String] {
return _myItems
}
}
let cont = Container()
cont.myItems.append("Neat") //not allowed
It uses a private stored property and a computed property that returns an immutable copy. It's not possible for a stored property to use custom getters.
A better way to expose mutable properties as immutable:
class Container {
private (set) internal var myItems: [String]
}