Swift, protocol variable properties [duplicate] - swift

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).

Related

Synthesised init for a struct with #propertyWrapper members

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
}

Is there a way to declare protocol property as private?

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

How to declare enums in Swift of a particular class type?

I am trying to create a class and use that class as the type for my new enum like shown below.
class Abc{
var age = 25
var name = "Abhi"
}
enum TestEnum : Abc {
case firstCase
case secondCase
}
I am getting following error in playground .
error: raw type 'Abc' is not expressible by any literal
So i tried conforming to RawRepresentable protocol like this.
extension TestEnum : RawRepresentable{
typealias RawValue = Abc
init?(rawValue:RawValue ) {
switch rawValue {
case Abc.age :
self = .firstCase
case Abc.name :
self = .secondCase
}
}
var rawValue : RawValue {
switch self{
case .firstCase :
return Abc.age
case .secondCase :
return Abc.name
}
}
}
I am getting following errors after this :
error: raw type 'Abc' is not expressible by any literal
error: instance member 'age' cannot be used on type 'Abc'
error: instance member 'name' cannot be used on type 'Abc'
What is the proper way to declare enums of a certain class type, not getting clear idea on this. Anyone help?
I'm not really sure what do you want to achieve, but take a look at my implementation, approach that I use in my projects:
class Abc {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
enum TestEnum {
case firstCase
case secondCase
var instance: Abc {
switch self {
case .firstCase: return Abc(age: 25, name: "John")
case .secondCase: return Abc(age: 20, name: "Marry")
}
}
}
//Usage:
let abc = TestEnum.secondCase.instance
print(abc.age) //prints '20'
From Docs
In particular, the raw-value type must conform to the Equatable
protocol and one of the following protocols:
ExpressibleByIntegerLiteral for integer literals,
ExpressibleByFloatLiteral for floating-point literals,
ExpressibleByStringLiteral for string literals that contain any number
of characters, and ExpressibleByUnicodeScalarLiteral or
ExpressibleByExtendedGraphemeClusterLiteral for string literals that
contain only a single character.
So make your class Abc to conform to Equatable and one of the above mentioned protocols. Here is an example
public class Abc : Equatable,ExpressibleByStringLiteral{
var age = 25
var name = "Abhi"
public static func == (lhs: Abc, rhs: Abc) -> Bool {
return (lhs.age == rhs.age && lhs.name == rhs.name)
}
public required init(stringLiteral value: String) {
let components = value.components(separatedBy: ",")
if components.count == 2 {
self.name = components[0]
if let age = Int(components[1]) {
self.age = age
}
}
}
public required convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
enum TestEnum : Abc {
case firstCase = "Jack,29"
case secondCase = "Jill,26"
}
Now you can initialize your enum like
let exEnum = TestEnum.firstCase
print(exEnum.rawValue.name) // prints Jack
For detailed discussion and example you can refer
https://swiftwithsadiq.wordpress.com/2017/08/21/custom-types-as-raw-value-for-enum-in-swift/
Have a look at Associated Values.
For your example:
class Abc {
var age = 25
var name = "Abhi"
}
enum TestEnum {
case age(Int)
case name(String)
}
Then you can use it like this:
var person = Abc()
...
var value = TestEnum.age(person.age)
switch value {
case .age(let age):
print("Age: \(age).")
case .name(let name):
print("Name: \(name).")
}
And for convenience you can write extension for enum, that will take your Abc object and convert it to enum value:
static func fromAbc(_ object: Abc) -> TestEnum? {
if object.age {
return TestEnum.age(object.age)
}
if object.name {
return TestEnum.name(object.name)
}
return nil
}
Note: in func fromAbc(object: Abc) -> TestEnum? you should replace conditions in if's to something that can be expressed as Bool (age > 0, etc).
As for the row values - in the doc it is stated that
Raw values can be strings, characters, or any of the integer or floating- point number types. Each raw value must be unique within its enumeration declaration.
And I'm not sure you can fit class there.

Why do will/didSet trigger initializer error, but not regular set/get?

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.

Swift: How to store a reference to Double, Int

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