"Cannot assign to the result of this expression" - How to set property of optional variable - properties

I have a question regarding Swift and optional properties.
Suppose I have the following code:
import Foundation
class MyClass: NSObject {
var parent: MyClass?
var string: String?
init() {
super.init()
}
}
let variable : MyClass = MyClass()
variable.string = "variable"
variable.parent?.string = "parent"
I get an error on the following line saying "Cannot assign to the result of this expression"
variable.parent?.string = "parent"
Now, I can suppress this error by replacing the question mark with an exclamation mark which, to my knowledge, makes Swift assume that an object will be there at runtime but it crashes because obviously there isn't an object.
Essentially, how can I set a property on an optional variable without doing something like "if variable.parent"?

To work with optional, you need to use optional chainning
let variable : MyClass = MyClass()
variable.string = "variable"
variable.parent = MyClass()
if let a = variable.parent {
a.string = "parent"
}
or if you are sure there is object there you can do
variable.parent!.string = "parent"
note that if you did not initialize parent you will get an error.

You've can't be assigning the string in a non-existent parent. It will be up to you how to avoid that. One thing for sure, either you will ensure the parent is bound (like getting it bound in some init() method) or you must use if variable.parent each time (even though you don't want to) or you must define a method like assignParentString() that is implemented to check on the parent.
func assignParentString (string: String) {
if let parent = self.parent {
parent.string = string
}
}
For example:
3> class MyClass {
4. var parent : MyClass?
5. var name : String?
6. func aps (name:String) {
7. if let parent = self.parent {
8. parent.name = name
9. }
10. }
11. }
12> var mc = MyClass()
mc: MyClass = {
parent = nil
name = nil
}
13> mc.name = "mc"
14> mc.aps ("mcp") // no error, name did not take
15> mc
$R3: MyClass = {
parent = nil
name = "mc"
}
16> mc.parent = MyClass()
17> mc.aps ("mcp")
18> mc
$R6: MyClass = {
parent = Some {
parent = nil
name = "mcp"
}
name = "mc"
}

Related

How can you enforce a setter method to be called on creating a object that can produce compile time warning or exception?

Suppose I've a class A which looked like this before:
class A(str:String){
// body
}
Now, I want to remove the parameter from the constructor instead I'll be using a setter for that value. Like below:
class A(){
lateinit var str:String
fun setStr(paramsString:String){
str = paramsString
}
}
As I am using a setter for assigning the value, it wont give me a compile time exception while creating the object of that class.
So, I tried something like below:
class A(){
lateinit var str:String
init{
setStr(strValue:String)
}
fun setStr(paramsString:String){
str = paramsString
}
}
This init block will be executed after creating the object of the class A where the setter method will be called.
But still what I want is to warn or throwing an exception when the object of the following class is created for calling the setter method.
This here
class A(public str:String){
// body
}
already has a setter, since yo can do
A("Bla"); println(a.str)
compile time errors would probably need some type checking (e.g. String? allows null, String checks for not null)
if you want to execute code on a.str="Blub" you do this:
class A(){
var str:String = ""
set(str) {
// check some stuff here, throw Exception
field = str // if all is well
}
}
you can also do similar for get() BTW.
Another method to do stuff on setting is using var a: String by Delegate.vetoable.... and other Delegate methods - giving you high control over what's happening on setting a new value.
class A {
var str: String = ""
set(str) {
// check some stuff here, throw Exception
field = str // if all is well
}
var observableDelegate: String by Delegates.observable("") { p, o, n ->
println("property: [$p], Old: [$o], New [$n]")
}
var notNullValue: String by Delegates.notNull<String>()
var vetoable: String by Delegates.vetoable("") { property, oldValue, newValue ->
println("property: $property, old: $oldValue, new: $newValue")
newValue != "bla" && oldValue < newValue
}
}
fun main() {
val a = A()
a.observableDelegate = "blub"
println("Set value ${a.observableDelegate}")
runCatching { a.notNullValue }.onFailure {
println("Like expected: value not set yet, exception: ${it.message}")
}
a.notNullValue = "fuddder"
println("Now notNullValue is set: ${a.notNullValue}")
println("Vetoable now: ${a.vetoable} - setting to \"a\"")
a.vetoable = "a"
println("Vetoable now: ${a.vetoable} - setting to \"b\"")
a.vetoable = "b"
println("Vetoable now: ${a.vetoable} - setting to \"a\" again - should not work")
a.vetoable = "a"
println("Vetoable now: ${a.vetoable}")
}
output:
property: [var com.savinggrains.mwh.web.rest.A.observableDelegate: kotlin.String], Old: [], New [blub]
Set value blub
Like expected: value not set yet, exception: Property notNullValue should be initialized before get.
Now notNullValue is set: fuddder
Vetoable now: - setting to "a"
property: var com.savinggrains.mwh.web.rest.A.vetoable: kotlin.String, old: , new: a
Vetoable now: a - setting to "b"
property: var com.savinggrains.mwh.web.rest.A.vetoable: kotlin.String, old: a, new: b
Vetoable now: b - setting to "a" again - should not work
property: var com.savinggrains.mwh.web.rest.A.vetoable: kotlin.String, old: b, new: a
Vetoable now: b

How to Observe LiveData with custom pair class in Kotlin

I am trying to observe the LiveData for the method which returns custom pair having 2 values. I want the observable to be triggered when I change either one of the values. But it is not getting triggered. Following is the code:
CustomPair.kt
data class CustomPair<T, V>(
var first : T,
var second : V
)
Observable:
falconViewModel.getPlanet1Name().observe(this) {
planet1.text = it.first
planet1.isEnabled = it.second
}
Getter and setter methods in ViewModel falconViewModel
private val planet1EnabledAndText = MutableLiveData<CustomPair<String, Boolean>>()
fun getPlanet1Name() : LiveData<CustomPair<String, Boolean>> {
return planet1EnabledAndText
}
fun setPlanet1Name(planetName : String, visibility : Boolean) {
planet1EnabledAndText.value?.run {
first = planetName
second = visibility
}
}
Can't we observe the value in such case? Please help what is wrong here.
It started working when I tried to set a new value of CustomPair instead of modifying the existing values in the object.
Replaced
planet1EnabledAndText.value = CustomPair(planetName, visibility)
with
planet1EnabledAndText.value?.run {
first = planetName
second = visibility
}
and it worked.

Swift: Typecasting Objects That Have Identical Selectors

The Situation
Suppose I have two classes:
class Foo: NSObject {
var specialProperty: String = "hello"
}
class Bar: NSObject {
var specialProperty: String = "goodbye"
}
Now suppose I'm working with a collection that I KNOW contains only Foo and Bar instances. I know that every object in the array will respond to the specialProperty selector, so in Objective-C I could just cast, like this:
for id thing in arrayOfThings
{
NSLog(#"specialProperty: %#", ((Foo *)thing).specialProperty);
}
How can I approach this in Swift? I cannot add a common superclass to Foo and Bar, nor can I add a protocol to them. In reality, Foo and Bar are NSManagedObject subclasses that represent model items in my app.
Selector?
I have considered this approach:
let collection: [AnyObject] // Assume this contains Foo and Bar instances.
let sel: Selector = #selector(setter: Foo.specialProperty)
for item in collection
{
if item.respondsToSelector(sel)
{
instance.perform(sel, with: "new value")
}
}
Will calling sel on instances of Bar work, even though I told Swift the type for the selector was Foo.? It seems like it should because the Objective-C selector mechanism doesn't care what the class of the Object is; that's not part of the selector signature. But I'm unsure if there's something in the Swift-ObjectiveC interaction that I'm overlooking.
Context
I am migrating this app from Objective-C.
The correct approach to this, in both Objective C and Swift, is to use a protocol:
protocol Special {
var specialProperty { get set }
}
class Foo: NSObject, Special {
var specialProperty: String = "hello"
}
class Bar: NSObject, Special {
var specialProperty: String = "goodbye"
}
let collection: [Special] = ...
for item in collection {
item.specialProperty = "new value"
}
I think you can consider this approach as well
let collection: [AnyObject] // Assume this contains Foo and Bar instances.
for item in collection
{
guard let aFoo = item as? Foo else {
guard let aBar = item as? Bar else { continue }
  aBar.specialProperty = "New value"
continue
}
aFoo.specialProperty = "New value"
}
Besides using protocol, I believe it can also be done using optional cast
for item in collection
{
if let foo = item as? Foo
{
// only when casting to Foo successfully
foo.specialProperty
}
if let bar = item as? Bar
{
// only when casting to Bar successfully
bar.specialProperty
}
}

Accessing field of a different instance of the same class in Kotlin

Consider this Kotlin code:
var parent: T? = null
get() = if (isParent) this as T else field
set(value) { field = if (value == null) null else value.parent }
val isParent: Boolean
get() = parent == null
var description = ""
get() = if (isParent) field else parent!!.description
set(value) { if (isParent) field = value else parent!!.description = value }
Assume that isParent returns true if this instance is a parent instance. If not getParent() will return the parent instance. In Java you are allowed to access directly field of a different instance of same class like this:
String getDescription() { return getParent().description; }
void setDescription(String value) { getParent().description = value; }
(I am not saying that is a best thing to do, I simplified it for demostration). Comparing to Java, it would be nice to be able to do following:
var description = ""
get() = parent.field
set(value) { parent.field = value }
However this does not work and unfortunately it makes the code less readable. Especially if you have a lot of such variables, which are bound to this parent.
A backing field of a property can only be accessed from a getter or setter of that property, and only for the instance on which the getter or setter has been invoked. If you need to provide multiple ways to access an attribute of a class, you need to define two distinct properties, one of which has a backing field to store the data and another has a getter and setter referring to the first property.
class Foo {
var parent: Foo? = null
val parentOrSelf: Foo get() = parent ?: this
private var _description: String? = null
var description = ""
get() = parentOrSelf._description
set(value) { parentOrSelf._description = value }
}

Could not cast value of type XX to XX in swift 3

In Swift 3 contains method is always giving error. In the below code if annotation is MKAnnotation is passed and goest to next line. Then it gives error. I have searched a lot but not able to find the problem. Any solution for this issue?
Class declaration:
open class FBAnnotation : NSObject {
open var coordinate = CLLocationCoordinate2D(latitude: 52.0936440, longitude: 4.3592340)
open var title: String? = ""
open var annotationIndex: Int?
}
extension FBAnnotation : MKAnnotation {
}
Usage:
do {
if annotation is MKAnnotation {
if try node.annotations.contains(where: annotation as! (MKAnnotation) throws -> Bool) {
try node.annotations.remove(at: node.annotations.index(where: annotation as! (MKAnnotation) throws -> Bool)!)
node.count -= 1
return true
}
}
} catch {
return false
}
This works in a playground, no casting required. You cannot pass an annotation as the where parameter. You must pass in a function that declares whether or not the annotation is the one you are looking for. Here I consider them a match if they have the same coordinates, although your match criteria may be different.
var annotations = [MKAnnotation]()
var annotation: Any? = nil
if let annotation = annotation as? MKAnnotation {
if let index = annotations.index(where: {
$0.coordinate.latitude == annotation.coordinate.latitude &&
$0.coordinate.longitude == annotation.coordinate.longitude
}) {
annotations.remove(at: index)
}
}