I want to conform to a protocol, as well as to hide its conformed properties to be accessed (declare them as private).
Consider the following:
protocol P {
var value: String { get }
init(value: String)
}
class C: P {
var value: String
required init(value: String) {
self.value = value
}
}
I would create a C object:
let myObject = C(value: "Hello World")
myObject.value = "New Value"
Based on that I have 2 questions:
Now, if I tried to declare value as private:
private var value: String { get }
the compiler will throw an error:
'private' modifier cannot be used in protocols
with a fix suggestion to replace the private with internal.
How can I prevent value from being accessible by saying myObject.value? if there is no way, what's the reason of this limitation?
Conforming to
protocol P {
var value: String { get }
init(value: String)
}
requires a gettable property value with default access. If write access to the
property in the conforming class should be restricted to the class itself
then you can declare it as in Swift readonly external, readwrite internal property:
class C: P {
private(set) var value: String
required init(value: String) {
self.value = value
}
}
let myObject = C(value: "Hello World")
print(myObject.value) // OK
myObject.value = "New Value" // Error: Cannot assign to property: 'value' setter is inaccessible
And if the property should only be set in initializers then make it
a constant:
class C: P {
let value: String
required init(value: String) {
self.value = value
}
}
let myObject = C(value: "Hello World")
print(myObject.value) // OK
myObject.value = "New Value" // Error: Cannot assign to property: 'value' is a 'let' constant
Related
What's the magic attached to SwiftUI's #State property wrapper which means I can I do this?:
struct A {
#State var s: String
}
let a = A(s: "string") //uses a synthesised init for `A` which allows me to init A with the underlying type of `A.s` - a `string`
whereas if I roll my own #propertyWrapper, I can't?
#propertyWrapper
struct Prop<Value> {
var value: Value
var wrappedValue: Value {
get { value }
set { value = newValue }
}
}
struct B {
#Prop var s: String
}
let b = B(s: "string") // Compiler error: `Cannot convert value of type 'String' to expected argument type 'Prop<String>'`
let b = B(s: Prop(value: "string")) // Works, but is ugly
As documented in:
Swift evolution proposals
Deep in the language guide
... you can get the compiler to do this for you, as it does with #State - just add a specific magic init(wrappedValue:) to your property wrapper definition:
#propertyWrapper
struct Prop<Value> {
var value: Value
var wrappedValue: Value {
get { value }
set { value = newValue }
}
// magic sauce
init(wrappedValue: Value) {
self.value = wrappedValue
}
}
struct B {
#Prop var s: String
}
let b = B(s: "string") // Now works
Incidentally, this also allows you to assign default values for your wrapped properties in the struct definition:
struct B {
#Prop var s: String = "default value" // Works now; would have thrown a compiler error before
}
This compiles fine:
class Foo {
var number = 0
var name: String
init(name: String){ self.name = name }
}
class SubFoo: Foo {
init(_ x: Int){
super.init(name: "abc")
}
var a: Int {
set {}
get {
return 0
}
}
}
a is a computed property and thus does not require initialization.
But this does not compile:
class SubFoo: Foo {
init(_ x: Int){
super.init(name: "abc")
}
var a: Int {
willSet {}
didSet {
print(oldValue)
}
}
}
The SubFoo init method shows this:
error: property 'self.a' not initialized at super.init call
super.init(name: "sdf")
a is still not a "real" property, so what exactly is the compiler expecting here?
Strangely I can silence the error by doing this:
init(_ x: Int){
a=0
super.init(name: "sdf")
}
Why would the a=0 be required for a willSet but not a normal set?
While willSet and didSet appear in brackets after the property, similar to computed methods get and set, they are not referring to a computed property behavior. willSet and didSet act on stored properties. This is also why you can provide a default value for them, but not for get/set:
var a: Int = 6 { // this line would not compile for a computed property
willSet {
}
didSet {
print(oldValue)
}
}
This is the reason for the compiler error in the init -- there was no initialization of the stored property.
I would like to store a reference to a primitive type (Double, Int) in Swift, so that i one variable is changed, the other one is changed, too. Here is an example:
class MyClass {
var value: Double?
}
var myValue = 1.0
var instance = MyClass()
instance.value = myValue // <-- how to set a reference?
myValue = 2.0 // => I want instance.value to change to 2.0
instance.value = 3.0 // => I want myValue to change to 3.0
Is that possible?
You can use class-holder:
class Ref<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
Or try to read about In-Out Parameters and maybe it can help you in some way:
In-Out Parameters
Variable parameters, as described above, can only be changed within
the function itself. If you want a function to modify a parameter’s
value, and you want those changes to persist after the function call
has ended, define that parameter as an in-out parameter instead.
You write an in-out parameter by placing the inout keyword at the
start of its parameter definition. An in-out parameter has a value
that is passed in to the function, is modified by the function, and is
passed back out of the function to replace the original value.
You can only pass a variable as the argument for an in-out parameter.
You cannot pass a constant or a literal value as the argument, because
constants and literals cannot be modified. You place an ampersand (&)
directly before a variable’s name when you pass it as an argument to
an inout parameter, to indicate that it can be modified by the
function.
func swapTwoInts(inout a: Int, inout _ b: Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3"
The closest thing in Swift I can think of like this is inout variables. They are allowed in Swift as a constructor argument (but saving them to a variable stops them from working):
class Demo {
var value: Double
init(inout value: Double) {
value++
self.value = value
}
}
var value = 1.0
let demo = Demo(value: &value)
print(value) // 2.0
demo.value++
print(value) // 2.0
Thus, I don't believe the syntax you want is possible. However, maybe inout parameters might be a substitute if you can re-work the problem.
I use this:
#propertyWrapper public struct Inout<T> {
public init(wrappedValue: T) {
storage = .init(value: wrappedValue)
}
private let storage: Storage
public var wrappedValue: T {
nonmutating get { storage.value }
nonmutating set { storage.value = newValue }
}
private class Storage {
init(value: T) {
self.value = value
}
var value: T
}
}
Example:
struct Example {
init(value: Inout<Int>) {
_value = value
}
#Inout var value: Int
func performAction() {
value += 19
}
}
let value = Inout(wrappedValue: 50)
let example = Example(value: value)
example.performAction()
print(value.wrappedValue) // 69
Class A provides a string value. Class B has two members of A type inside itself, and provide a computed property "v" to choose one of them.
class A {
var value: String
init(value: String) {
self.value = value
}
}
class B {
var v1: A?
var v2: A = A(value: "2")
private var v: A {
return v1 ?? v2
}
var value: String {
get {
return v.value
}
set {
v.value = newValue
}
}
}
This code is simple and it works. Since both the A and B have a member "value", I make it a protocol like this:
protocol ValueProvider {
var value: String {get set}
}
class A: ValueProvider {
var value: String
init(value: String) {
self.value = value
}
}
class B: ValueProvider {
var v1: ValueProvider?
var v2: ValueProvider = A(value: "2")
private var v: ValueProvider {
return v1 ?? v2
}
var value: String {
get {
return v.value
}
set {
v.value = newValue // Error: Cannot assign to the result of the expression
}
}
}
If I change the following code
v.value = newValue
to
var v = self.v
v.value = newValue
It works again!
Is this a bug of Swift, or something special for the property of protocols?
You have to define the protocol as a class protocol:
protocol ValueProvider : class {
var value: String {get set}
}
Then
var value: String {
get { return v.value }
set { v.value = newValue }
}
compiles and works as expected (i.e. assigns the new value to the
object referenced by v1 if v1 != nil, and to the object
referenced by v2 otherwise).
v is a read-only computed property of the type ValueProvider.
By defining the protocol as a class protocol the compiler knows
that v is a reference type, and therefore its v.value
property can be modified even if the reference itself is a constant.
Your initial code example works because there the v property has
the type A which is a reference type.
And your workaround
set {
var tmp = v1 ?? v2
tmp.value = newValue
}
works because (read-write) properties of variables can be set in
any case (value type or reference type).
I think var value: T = nil is causing error below because XCode can't convert nil value to the generic type T.
class Node<T> {
var value: T = nil
var next: Node
init(value: T) {
self.value = value
self.next = Node()
}
init() {
self.next = Node()
}
}
The error message reads
Could not find an overload for '_coversion' that accepts the supplied
arguments
Is there a way to assign nil value to a variable in Swift?
You need to declare the variable as optional:
var value: T? = nil
Unfortunately this currently seems to trigger an unimplemented compiler feature:
error: unimplemented IR generation feature non-fixed class layout
You can work around it by declaring T with a type constraint of NSObject:
class Node<T:NSObject> {
var value: T? = nil
var next: Node
init(value: T) {
self.value = value
self.next = Node()
}
init() {
self.next = Node()
}
}
Try using
var value: T? = nil
The question mark makes the variable an optional, meaning that it can either have a value or be nil.