My class has a property of type NSURL
that is initialized from a string. The string is known at compile time.
For the class to operate appropriately, it must be set to its intended value at initialization (not later), so there is no point in defining it as an optional (implicitly unwrapped or otherwise):
class TestClass: NSObject {
private let myURL:NSURL
...
Assuming that NSURL(string:)
(which returns NSURL?
) will never fail if passed a valid URL string that is known at compile time, I can do something like this:
override init() {
myURL = NSURL(string: "http://www.google.com")!
super.init()
}
However, I somehow don't feel comfortable around the forced unwrapping and would like to guard the URL initialization somehow. If I try this:
guard myURL = NSURL(string: "http://www.google.com") else {
fatalError()
}
Value of optional type 'NSURL?' not unwrapped; did you mean to use '!' or '?'?
(Note: there's no way to add a !
or ?
anywhere the code above that will fix the error. Conditional unwrapping only happens with guard let...
guard var...
, and myURL
is already defined)
I understand why this fails: Even a successful call to NSURL(string:)
is returning the (valid) NSURL
wrapped inside an optional NSURL?
, so I still need to unwrap it somehow before assigning to myURL
(which is non-optional, hence not compatible for assignment as-is).
I can get around this by using an intermediate variable:
guard let theURL = NSURL(string: "http://www.google.com") else {
fatalError()
}
myURL = theURL
...but this is obviously not elegant at all.
What should I do?
You write a failable initializer by placing a question mark after the init keyword ( init? ). Note: You cannot define a failable and a nonfailable initializer with the same parameter types and names. A failable initializer creates an optional value of the type it initializes.
The error "Property has no initializer and is not definitely assigned in the constructor" occurs when we declare a class property without initializing it. To solve the error, provide an initial value for the class property, e.g. name: string = 'James'; or use a non-null assertion.
To initialize an object in TypeScript, we can create an object that matches the properties and types specified in the interface. export interface Category { name: string; description: string; } const category: Category = { name: "My Category", description: "My Description", };
Update Another approach, that doesn't use guard
, would be to use a switch
, as optionals map to the Optional
enum:
init?() {
switch URL(string: "http://www.google.com") {
case .none:
myURL = NSURL()
return nil
case let .some(url):
myURL = url
}
}
although you'd still get a url
local variable.
Original answer
You can declare your initializer as a failable one and return nil
in case the url string parsing fails, instead of throwing a fatal error. This will make it more clear to clients your the class that the initializer might fail at some point. You still won't get rid of the guard
, though.
init?() {
guard let url = URL(string: "http:www.google.com") else {
// need to set a dummy value due to a limitation of the Swift compiler
myURL = URL()
return nil
}
myURL = url
}
This add a little complexity on the caller side, as it will need to check if the object creation succeeded, but it's the recommended pattern in case the object initializer can fail constructing the object. You'd also need to give up the NSObject
inheritance as you cannot override the init
with a failable version (init?
).
You can find out more details about failable initializers on the Swift blog, Apple's documentation, or this SO question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With