Propagate an Optional Through a Function (Or Init) in Swift

Propagate an optional through a function (or Init) in Swift

You can simply use Optional's map(_:) method, which will return the wrapped value with a given transform applied if it's non-nil, else it will return nil.

let f : Float? = 2

// If f is non-nil, return the result from the wrapped value passed to Double(_:),
// else return nil.
let d = f.map { Double($0) }

Which, as you point out in the comments below, can also be said as:

let d = f.map(Double.init)

This is because map(_:) expects a transformation function of type (Float) -> Double in this case, and Double's float initialiser is such a function.

If the transform also returns an optional (such as when converting an String to a Int), you can use flatMap(_:), which simply propagates a nil transform result back to the caller:

let s : String? = "3"

// If s is non-nil, return the result from the wrapped value being passed to the Int(_:)
// initialiser. If s is nil, or Int($0) returns nil, return nil.
let i = s.flatMap { Int($0) }

Can I check if an optional is nil by using it as an argument?

You can use the

public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

method of Optional:

if let parsedZoomURL = zoomURL.flatMap( { URL(string: $0) }) {
//do stuff
}

or shorter (as someone noticed in a now deleted comment):

if let parsedZoomURL = zoomURL.flatMap(URL.init) {
//do stuff
}

The optional binding succeeds only if zoomURL is not nil
and the closure (which is then called with the unwrapped value
of zoomURL) does not return nil.

Is there a neater alternative to if-let to call a function on an Optional?

You can use map or flatMap on Optional types. flatMap basically evaluates the closure passed to it if and only if the value exist.

Here is a sample,

let maybeValue: String? = "awesome swift"

let val = maybeValue.flatMap { $0.uppercased() }

Why isn't a implicit optional required in a class's init method?

An implicit optional is still an optional, so it can be nil and it does not need to be set at the time of initialisation. You will, however, get still get an exception if you access it unconditionally when it is nil.

If your Phone class were:

class Phone {
let model: String
var owner: User!
init(model: String) {
self.model = model
print("Phone \(model) is initialized")
}
deinit {
print("Phone \(model) is being deallocated")
}
}

and you said

var aPhone = Phone(model:"iPhone7")
let ownerName = aPhone.owner.name

Then you would get an exception on the second line because the implicitly unwrapped optional is nil. That line is the equivalent of writing let ownerName = aPhone.owner!.name if owner were of type User?.

Implicitly unwrapped optionals are very useful where a value cannot be assigned at initialisation, but a value will be assigned shortly thereafter, as it avoids the need to continually unwrap the variable.

For example, implicitly unwrapped optionals are often used for @IBOutlet properties in a view controller. The property will be set by the storyboard load process, but after the view controller object is initialised. Since you know that the property will have a value by the time any of your code runs, the use of an implicitly unwrapped optional is safe and you avoid having to continually unwrap the property.

Your sample Phone class is not a good use of implicitly unwrapped optionals; either a simple optional (as you have shown) or a required initialiser argument is appropriate depending on whether phones can be unowned or not.

Concatenate literal with Optional String

You can use the map method of Optional:

let username = name.map { "@" + $0 }

If name is nil then the closure is not executed and the result is nil. Otherwise the closure is evaluated with $0 set to the unwrapped name.

Swift why unwrapping optional by guarding against NIL is not working

The type of s is still Optional, so whether you did a nil check or not is irrelevant. The nil check is done in runtime, while the type system is doing a compile-time check. The only way to ensure s can never be nil is via optional binding if let or guard let.

Swift how to create and test an object with nil properties?

You can make the init parameter an optional String?, with a default
value
nil:

class Contact {
var displayName: String?

init(displayName: String? = nil) {
self.displayName = displayName
}
}

let contact1 = Contact()
let contact2 = Contact(displayName: "John")

The same works for the Contacts class:

class Contacts {
func create(displayName: String? = nil) -> Contact {
return Contact(displayName: displayName)
}
}

let contacts = Contacts()
let contact3 = contacts.create()
let contact4 = contacts.create("Mary")

How do you create a SwiftUI view that takes an optional secondary View argument?

November 2021 update (Works in Xcode 11.x, 12.x, and 13.x)

After some thought and a bit of trial and error, I figured it out. It seems a bit obvious in hindsight.

struct SomeCustomView<Content>: View where Content: View {
let title: String
let content: Content

init(title: String, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content()
}

// returns a new View that includes the View defined in 'body'
func sideContent<SideContent: View>(@ViewBuilder side: @escaping () -> SideContent) -> some View {
HStack {
self // self is SomeCustomView
side()
}
}

var body: some View {
VStack {
Text(title)
content
}
}
}

It works with or without the method call.

SomeCustomView(title: "string argument") {
// some view
}

SomeCustomView(title: "hello") {
// some view
}.sideContent {
// another view
}

Previous code with subtle bug: body should be self

    func sideContent<SideContent: View>(@ViewBuilder side: @escaping () -> SideContent) -> some View {
HStack {
body // <--- subtle bug, updates to the main View are not propagated
side()
}
}

Thank you Jordan Smith for pointing this out a long time ago.



Related Topics



Leave a reply



Submit