More elegant way to assign optionals to a lazy property

Viewed 173

Imagine we have a lazy imageView and we want to initialize it only if there is an image to assign. So we need to check for the image value each time:

lazy var imageView1 = UIImageView()
lazy var imageView2 = UIImageView()

var image: UIImage?
var someOptional: SomeType?

func addImage1IfNeeded() {
    guard let image = image else { return }
    imageView1.image = image
} 

func addImage2IfNeeded() {
    guard let image = someOptional?.someChildOptional?.image else { return }
    imageView2.image = image
} 

So if we have lots of optionals and lazy variables and some optional chaining situations, we will have thons of repetitive codes.

What is more elegant way to do this? (Maybe using an operator? an extension on optional?)

Please note that This is not for optimization. This example is summarized and the original issue is happening when you want to not adding UI elements if there is no data to present. Also in other situations.

2 Answers

Using keypaths, I've been able to extract what you are doing into a function:

func assignNonNilImage<R, V>(_ root: R, _ keyPath: ReferenceWritableKeyPath<R, V>, _ value: V?) {
    if let nonNilValue = value {
        root[keyPath: keyPath] = nonNilValue
    }
}

Usage:

class Foo {
    lazy var imageView = UIImageView()
    var image: UIImage?

    func f() {

        // here!
        assignNonNilImage(self, \.imageView.image!, image)
    }
}

I'm not sure if it's any "better" though...

You can also write this as a method in your class:

func assignNonNilImage<V>(_ keyPath: ReferenceWritableKeyPath<Foo, V>, _ value: V?) {
    if let nonNilValue = value {
        self[keyPath: keyPath] = nonNilValue
    }
}

One way is to use an operator like this:

infix operator ?=>
func ?=>(lhs: Any?, rhs: @autoclosure ()->()) {
    guard lhs != nil else { return }
    rhs()
}

Usage:

image ?=> (imageView1.image = image)

I don't know if we can combine the first and last argument since they are alwayse the same.


Update - Option 2

Following @Sweeper's comment:

func ?=><T>(lhs: T?, rhs: (T)->()) {
    guard let lhs = lhs else { return }
    rhs(lhs)
}

Usage:

someOptional?.someChildOptional?.image = { imageView2.image = $0 }

It's better for long optional changings but yet requires curly braces and $ argument (that I forgot it's name)

Related