Accessing subclass members using KeyPath - swift

I was trying to access subclass members using KeyPath when I encountered something strange. Look at these 2 simple classes:
class A {
var a:String {
get {
return "str"
}
}
}
class B: A {
override var a:String {
get {
return "str1"
}
}
var c = 10
}
Now if I have the following code:
var m: A = B()
var k = \B.a
print(m[keyPath: k])
I will get a runtime error. It seems that you cannot access to subclass members using a WriteableKeyPath. But the following code works:
var m: A = B()
var k: AnyKeyPath = \B.a
print(m[keyPath: k]!)
I can use this code to access both \B.a and \B.c. Any idea why it is like this?
Since returned object by using AnyKeyPath is immutable, I will not be able to use it for updating object. I wonder if there is any workaround for updating subclass members using a KeyPath on parent class or not.
Update:
In order to elaborate more, here is another example:
var v: UIView = UILabel()
var k = \UILabel.text
v[keyPath: k] = "test" // Error cannot do this
I think this can be very useful.

Related

Why does Swift allow assigning a struct property to itself, but not a class property?

Swift has this awesome error that shows up when you try to do something of the form x = x:
class Foo {
var foo = 1
}
var a = Foo()
a.foo = a.foo // error
This helped me avoid typos where the class had two similarly named properties, and I want to assign one to the other, but mistakenly typed the same one twice.
However, in this particular case:
struct Foo {
var foo = 1 {
didSet {
print("Did set")
}
}
mutating func f() {
foo = foo
}
}
var a = Foo()
a.foo = a.foo
It successfully compiles! There isn't even an error on the foo = foo line! If I change Foo to a class, or if I remove the didSet, then the expected error appears. It's just this struct + didSet combination that makes the compiler think "yeah, a.foo = a.foo makes a lot of sense! Let me allow that!"
I found this related post, which is about how to stop getting this error, rather than how to get it.
I also looked on bugs.swift.org, but there were only 3 results, and none of them are related.
I'm using Swift 5.3.2 and Xcode 12.4.
Is there any reason why assigning a struct property to itself is more "OK" than assigning a class property to itself?

How can i pass class as a parameter to a function in Swift?

Let us consider i have two different classes.
class A {
var something = "Hello"
}
class B {
var something = "World"
}
Now
class C {
func request() {
//Call with class A or B it can contain any class. I can call either class A or B depending on condition
update(myClass: A or B)
}
func update(myClass:A or B ) {
print(myClass.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
Plz help me achieve this using Swift
You cannot declare a function in Swift that could accept an input argument of several different types, so you cannot declare a type as A or B. However, you don't actually need this to solve your specific problem.
Since you want to access a common property of the two class instances, you should declare that property in a protocol, make both classes conform to that protocol, then make the function take an input argument of the protocol type.
protocol SomethingProtocol {
var something: String { get }
}
class A: SomethingProtocol {
let something = "Hello"
}
class B: SomethingProtocol {
let something = "World"
}
class C {
func request() {
//Call with class A or B it can contain any class. I can call either class A or B depending on condition
update(something: A())
update(something: B())
}
func update(something: SomethingProtocol) {
print(something.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
Use a protocol
protocol MyProtocol: class {
var something: String { get set }
}
class A: MyProtocol {
var something = "Hello"
}
class B: MyProtocol {
var something = "world"
}
class C {
func update(myClass:MyProtocol ) {
print(myClass.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
usage:
let a = A()
let b = B()
let c = C()
print(c.update(myClass: a))
print(c.update(myClass: b))
Output:
hello
world
Create a protocol that both A and B conforms to and use it as the parameter type in update()
protocol SomeProtocol {
var something: String {get set}
}
func update(_ o: SomeProtocol) {
print(o.something)
}
Let it be known that I think using a protocol is the cleanest option that will best solve your problem.
However, it is possible to use Any to pass any object as a parameter, this will require checking which class you are dealing with inside your update method.
Something like this...
class C {
func update(myClass: Any) {
if let a = myClass as? A {
print(a.something)
}
if let b = myClass as? B {
print(b.something)
}
}
}
This might be neater as a switch - ref
class C {
func update(myClass: Any) {
switch myClass {
case let a as A:
print(a.something)
case let b as B:
print(b.something)
default:
print("not a thing")
}
}
}

Recursive type definitions in Swift

I have encountered what would seem like an extremely simple issue to solve, and I think I may be missing something.
The program crashes when attempting to create a recursive variable (?) with the typed to a class while being a member of the said class. For example:
class A {
var x: A
init() {
x = A()
}
}
Checking the crash log suggests a recursion of sorts, with init() being called infinitely.
Is there a proper method/syntax to solve this sort of problem, given that I need the variable x to be typed to class A, and the initializer must initialize variable x to class A?
It's obvious that at some step you should left property x uninitialized. So, thats better to declare it as Optional, and initialize it after instance was created:
class A {
var x: A?
}
let mainObject = A()
let nestedObject = A()
mainObject.x = nestedObject
Not sure but i think you are looking for this
class A {
var x: A?
init() {
}
anothermethod() {
x = A()
}
}
and you can call this method like
let a = A()
a.anothermethod()

Why I cannot place a designated initliazer in to a extension of a class?

It works fine when I put a designated initializer into the structure's extension (please see the example as follow)
struct BaseOne {
var a = 12
var b = 22
}
extension BaseOne {
init(a: Int){
self.a = a
self.b = 231
}
}
However, when I do this for the class, thing were start to gone wrong
class BaseOne {
var a = 12
var b = 22
}
extension BaseOne {
init(a: Int){ // Error message poped up here
self.a = a
self.b = 231
}
}
Can someone explain this for me?
Thanks
from the document
All of the designated initializers for a class must be written within the class definition itself, rather than in an extension, because the complete set of designated initializers is part of the interface contract with subclasses of a class.
Edit: Document link
https://github.com/apple/swift/blob/master-next/docs/ObjectInitialization.rst
This is because swift automatically generates initializers for you when working with structs. So this is valid
struct BaseOneStruct {
var a: Int
var b: Int
}
let a = BaseOne(a: 12, b: 32)
even though there is no initializer. Classes do not do this for you however. For your code to work you would have to do something like this
class BaseOneClass {
var a = 12
var b = 22
init() { }
}
extension BaseOneClass {
convenience init(a: Int){
self.init()
self.a = a
self.b = 231
}
}
Also note that you are able to use the empty init BaseOneStruct() because you set values for your variables at their definition

Referencing another variable in a global variable in swift

I don't understand why the following doesn't work in Swift:
class SomeClass {
var foo = 1
var bar = self.foo + 1
}
and what's the way around it?
It doesn't work because you cannot use self in that scope to define default values for properties. I believe it is due to the fact that you cannot use self before the object is properly initialized. You could use an explicit initializer instead.
class SomeClass {
var foo: Int
var bar: Int
init() {
self.foo = 1
self.bar = self.foo + 1
}
}
You can, however, access static members.
class SomeClass {
static let initialValue = 1
var foo = initialValue
var bar = initialValue + 1
}