Despite some experience in mobile development (including the use of Swift), regularly on the basis of swift options there were situations when I knew what to do, but did not quite clearly understand why . It was necessary to be distracted and go deep into the documentation - the number of “notes on the margins” was replenished with depressing periodicity. At a certain point, they reached a critical mass, and I decided to streamline them in a single comprehensive guide. The material turned out to be quite voluminous, since an attempt was made to reveal the topic in as much detail as possible. The article will be useful for both beginner Swift developers and experienced professionals from the world of Objective-C - there is a non-zero probability that the latter will find something new for themselves. And if they do not find it, they will add their own new in the comments, and everyone will benefit.
Optionals are a convenient mechanism for handling situations where the value of a variable may be missing. The value will be used only if it is.
First, the nil
equality / inequality test applies only to nullable types and is not applicable to primitive types, structures, and enums. To indicate the absence of a value for a variable of a primitive type, it is necessary to introduce special values, such as NSNotFound .
NSNotFound should not only be considered as a special value, but also ensure that it is not included in the set of acceptable values ​​of the variable. The situation is further complicated by the fact that NSNotFound is considered equal to NSIntegerMax , i.e. may have different values ​​for different (32-bit / 64-bit) platforms. This means that NSNotFound cannot be directly written to files and archives or used in Distributed Objects .
Accordingly, the user of this variable must take into account that special values ​​are possible. In Swift, even a primitive type can be used in an optional style, that is, explicitly indicating that values ​​may not be.
Secondly: explicit optionality is checked at the compilation stage, which reduces the number of errors in runtime. An optional variable in Swift cannot be used in the same way as a non-optional variable (with the exception of implicitly derived options, see the Implicit Unwrapping section for details ). The optional must either be forced to convert to the usual value, or use special transformative idioms, such as if let
, guard let
and ??
. Options in Swift implement not just a check, but the whole paradigm of an optional type in type theory .
Thirdly, optionals are syntactically more concise than checking for nil
, which is especially well seen on the chains of optional calls - the so-called Optional Chaining .
An optional in Swift is a special container object that can contain either nil
or an object of a particular type, which is specified when the container is declared. These two states are denoted by the terms None and Some, respectively. If at creation of an optional variable the assigned value is not specified, then nil
assigned by default.
In the documentation, the default value in the case of the absence of an explicit assignment is not mentioned, but it is said that it represents the absence of a value . If an optional variable is declared without an explicit assignment (some Some was not assigned), then it logically follows that None is implicitly assigned — the optionals have no third "uninitialized" state.
Is the optional declared by a combination of type name and lexeme ?
. So, record Int?
Is a container declaration, an instance of which may contain nil
inside (state None Int ) or a value of type Int
(state Some Int ). That is why when converting an Int?
in Int
uses the term unwrapping instead of cast , i.e. The "containerized" essence of the option is underlined. A nick in Swift denotes a state of None that can be assigned to any option. This logically leads to the impossibility of assigning nil
(state None ) to a variable that is not optional.
In fact, an optional is a system enumeration:
public enum Optional<Wrapped> : ExpressibleByNilLiteral { /// The absence of a value. /// /// In code, the absence of a value is typically written using the `nil` /// literal rather than the explicit `.none` enumeration case. case none /// The presence of a value, stored as `Wrapped`. case some(Wrapped) /// Creates an instance that stores the given value. public init(_ some: Wrapped) ... /// Creates an instance initialized with `nil`. /// /// Do not call this initializer directly. It is used by the compiler // when you initialize an `Optional` instance with a `nil` literal. public init(nilLiteral: ()) ... }
The Optional
enumeration has two possible states: .none
and some(Wrapped)
. Record Wrapped?
processed by the preprocessor ( Swift's type system ) and transformed into Optional<Wrapped>
, i.e. The following entries are equivalent:
var my_variable: Int?
var my_variable: Optional<Int>
The token nil
in fact denotes Optional.none
, i.e. The following entries are equivalent:
var my_variable: Int? = nil
var my_variable: Optional<Int> = Optional.none
var my_variable = Optional<Int>.none
The Optional
enumeration has two constructors. The first init(_ some: Wrapped)
constructor init(_ some: Wrapped)
takes as input the value of the corresponding type, i.e. The following entries are equivalent:
var my_variable = Optional(42) // .some- Int
var my_variable = Optional<Int>(42) // Int
var my_variable = Int?(42) // Int
var my_variable: Int? = 42 // Int
var my_variable = Optional.some(42) // Int
var my_variable = Optional<Int>.some(42) //
The second init(nilLiteral: ())
constructor init(nilLiteral: ())
is an implementation of the ExpressibleByNilLiteral
protocol
public protocol ExpressibleByNilLiteral { /// Creates an instance initialized with `nil`. public init(nilLiteral: ()) }
and initializes the optional variable with the .none
state. This constructor is used by the compiler. According to the documentation it is not recommended to call it directly.
var test = Optional<Int>(nilLiteral: ()) //
which is logical, since the conversion of the empty tuple Void ()
to nil
somewhat unclear.
Instead, this constructor should be used.
var my_variable: Int? = nil // var my_variable: Int? = Optional.none
or not to use explicit assignment at all
var my_variable: Int?
since nil
will be assigned by default.
The Optional<Wrapped>
enumeration also contains the unsafelyUnwrapped property, which provides read access to the .some
optional:
public enum Optional<Wrapped> : ExpressibleByNilLiteral { ... /// The wrapped value of this instance, unwrapped without checking whether /// the instance is `nil`. public var unsafelyUnwrapped: Wrapped { get } }
If the optional is in the .none
state, a call to unsafelyUnwrapped
will result in a serious program crash .
In debug build -Onone debug mode, there will be a runtime error:
_fatal error: unsafelyUnwrapped of nil optional_
In the release build optimized build -O there will be a runtime error or undefined behavior . A safer operation is Force Unwrapping (or Explicit Unwrapping ) - the forced extraction of a .some
denoted by a token !
. Applying Force Unwrapping to an option in the .none
state will result in runtime error:
_fatal error: unexpectedly found nil while unwrapping an Optional value_
let my_variable1 = Int?(42) // 42, Optional Int let my_value1A = my_variable1! // 42, Int let my_value1B = my_variable1.unsafelyUnwrapped // 42, Int let my_variable2 = Int?.none // nil, Optional Int let my_value2A = my_variable2! // // -Onone, -O let my_value2B = my_variable2.unsafelyUnwrapped
It makes little sense to use a regular two-state enumeration. It is quite possible to implement a similar mechanism yourself: create an enum with two states and constructors for the corresponding values, add some postfix operator for Force Unwrapping (for example, as it is done here ), add the ability to compare with nil
or even invent your own nil
and m .d Options should be integrated directly into the language itself so that their use is natural, not alien. Of course, such integration can be considered as “syntactic sugar”, however, high-level languages ​​exist to write (and read) the code on them easily and pleasantly. The use of optionals in Swift implies a number of idioms or special language constructs that help reduce errors and make the code more concise. Such idioms include Implicit Unwrapping , Optional Chaining , Nil-Coalescing and Optional Binding .
Safe use of Force Unwrapping implies a preliminary check for nil
, for example, in the if condition:
// getOptionalResult() nil let my_variable: Int? = getOptionalResult() // Optional Int if my_variable != nil { // my_value .some- getOptionalResult() let my_value = my_variable! } else { // let my_value = my_variable! }
Sometimes it is obvious from the structure of the program that a variable is technically optional, but by the time it is first used it is always in a .some
state, i.e. is not nil
. To use an optional in a non-optional context (for example, passing it to a function with a non-optional type parameter), you have to constantly use Force Unwrapping with a preliminary check, which is boring and tedious. In these cases, implicitly unwrapped optional can be applied implicitly. An implicitly retrieved optional is declared by a combination of the type name and the token !
:
let my_variable1: Int? = 42 // Optional Int let my_variable2: Int! = 42 // Implicitly Unwrapped Optional Int var my_variable3: Int! = 42 // Implicitly Unwrapped Optional Int ... my_variable3 = nil // - nil ... func sayHello(times:Int) { for _ in 0...times { print("Hello!") } } sayHello(times: my_variable1!) // sayHello(times: my_variable1) // sayHello(times: my_variable2!) // , sayHello(times: my_variable2) // sayHello(times: my_variable3) //
In the call to sayHello(times: my_variable2)
extracting the value 42
from my_variable2
is still carried out, only implicitly . The use of implicitly retrievable options makes the code more readable — there are no exclamation marks that distract attention (it is likely that the reader will be troubled by the use of Force Unwrapping without prior verification). In practice, it is rather an anti-pattern, increasing the likelihood of error. The implicitly retrievable option forces the compiler to "close eyes" on the fact that the optional is used in a non-optional context. An error that can be detected at compile time (call sayHello(times: my_variable1)
) will manifest only in runtime (call sayHello(times: my_variable3)
). Explicit code is always better than implicit. It is logical to assume that such a reduction in the security code is required not only for the sake of eliminating exclamation marks, and this is true.
Implicitly optional options allow you to use self
in the constructor to initialize properties and, in addition,:
self
accessing) - otherwise the code will not be compiled;A vivid example of where you need to use self
in a constructor for initializing properties is given in the documentation :
class Country { let name: String var capitalCity: City! init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } } class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } } var country = Country(name: "Canada", capitalName: "Ottawa") print("\(country.name)'s capital city is called \(country.capitalCity.name)") // Prints "Canada's capital city is called Ottawa"
In this example, instances of the Country and City classes should have friend-to-friend references by the time the initialization is completed. Each country must have a capital and each capital must have a country. These connections are not optional - they are unconditional. During the initialization process of the country
object, it is necessary to initialize the capitalCity
property. To initialize capitalCity
you need to create an instance of the City class. The City constructor requires the corresponding Country instance as a parameter, i.e. requires access to self
. The difficulty is that the Country instance is not yet fully initialized, i.e. self
cannot be used.
This task has an elegant solution: capitalCity
declared a mutable implicitly retrievable option. Like any mutable optional, capitalCity
by default initialized to the nil
state, that is, by the time the City constructor is called, all the properties of the country
object have already been initialized. Requirements of two-stage initialization are met, the Country constructor is in the second phase - you can pass self
to the City constructor. capitalCity
is an implicit option, i.e. it can be accessed in a non-contextual context without adding !
.
A side effect of using an implicitly retrievable option is the “built-in” assert
: if capitalCity
for some reason, this will lead to runtime error and program crashes.
Another example of the justified use of implicitly retrievable @IBOutlet
: the context of their use implies that the .some
automatically assigned to the variable at the time of the first call. If this is not the case, a runtime error will occur. Automatic generation of code in Interface Builder creates properties with @IBOutlet
in the form of implicit @IBOutlet
. If this behavior is unacceptable, the property with @IBOutlet
can be declared as an explicit optional and always process .none
values ​​explicitly. As a rule, it is still better to get a “crash” right away than to do a long debugging in case of an accidentally untied @IBOutlet
property.
Optional Chaining is a process of successive calls on a chain, where each link returns an option. The process is interrupted at the first option in the nil
state — in this case the result of the entire call chain will also be nil
. If all the links in the chain are in the .some
state, then the resulting value will be optional with the result of the last call. To form the links of the chain is used token ?
which is placed immediately after the call that returns the option. Chain links can be any operations that return an optional: access to a local variable (as the first link), calls to properties and methods, access by index.
Optional shaping always works sequentially from left to right. Each next link is passed .some
previous link, while the resulting value of the chain is always optional, i.e. The chain works according to the following rules:
?
should be the next link;.none
state, then the chain aborts the call process and returns nil
;.some
state, then the chain gives the .some
link to the input of the next link (if there is one);.some
returned optional). // : — `country.mainSeaport?`, country.mainSeaport?.nearestVacantPier?.capacity // , `?` let mainSeaport = country.mainSeaport? // `nil` country = Country(name: "Mongolia") let capacity = country.mainSeaport?.mainPier?.capacity // — country = Country(name: "Finland") let nearestVacantPier = country.mainSeaport?.nearestVacantPier // — , capacity // country = Country(name: "Finland") let capacity = country.mainSeaport?.nearestVacantPier?.capacity
It is important to distinguish between chains of optional calls and nested options. An nested optional is formed when the .some
one optional is another optional:
let valueA = 42 let optionalValueA = Optional(valueA) let doubleOptionalValueA = Optional(optionalValueA) let tripleOptionalValueA = Optional(doubleOptionalValueA) let tripleOptionalValueB: Int??? = 42 // `?` let doubleOptionalValueB = tripleOptionalValueB! let optionalValueB = doubleOptionalValueB! let valueB = optionalValueB! print("\(valueA)") // 42 print("\(optionalValueA)") // Optional(42) print("\(doubleOptionalValueA)") // Optional(Optional(42)) print("\(tripleOptionalValueA)") // Optional(Optional(Optional(42))) print("\(tripleOptionalValueB)") // Optional(Optional(Optional(42))) print("\(doubleOptionalValueB)") // Optional(Optional(42)) print("\(optionalValueB)") // Optional(42) print("\(valueB)") // 42
Optional shaping does not increase the nesting level of the returned option. However, this does not exclude the situation when the resultant value of any link is an option with several levels of nesting. In such situations, to continue the chain you need to register ?
in an amount equal to the number of nesting levels:
let optionalAppDelegate = UIApplication.shared.delegate let doubleOptionalWindow = UIApplication.shared.delegate?.window let optionalFrame = UIApplication.shared.delegate?.window??.frame // '?' print("\(optionalAppDelegate)") // Optional( ... ) print("\(doubleOptionalWindow)") // Optional(Optional( ... )) print("\(optionalFrame)") // Optional( ... )
Generally speaking, it is not necessary that all levels of nesting be "deployed" using a token ?
. Part of them can be replaced by forced extraction !
That will reduce the number of "implicit" links in the chain. A separate question is whether this makes sense.
The UIApplication.shared.delegate?.window??.frame
chain actually consists of four links: UIApplication.shared.delegate?
, .frame
and two links, combined in one call .window??
. The second "double" link is represented by an option of the second level of nesting.
An important feature of this example is also a special way of forming a double optional, which differs from the way of forming a doubleOptionalValue
in the previous example. UIApplication.shared.delegate!.window
is an optional property in which the optional is returned. The optionalness of a property means that the property itself may be missing, and not just the .some
the optional returned from the property. An optional property, like all other properties, can return any type, not just an optional one. Optionality of this kind is formed in the @ objc protocols with the optional
modifier:
public protocol UIApplicationDelegate : NSObjectProtocol { ... @available(iOS 5.0, * ) optional public var window: UIWindow? { get set } // optional ... }
In protocols with optional properties and methods (otherwise, optional requirements), the @objc
modifier @objc
specified for each optional requirement and for the protocol itself. This requirement does not apply to the UIApplicationDelegate protocol from the example above, since it is translated to Swift from the system library on Objective-C. Calling an unfulfilled optional request on an object that accepts such a protocol returns an optional type of the corresponding type in the .none
state. Calling an implemented optional request returns an optional type of the corresponding type in the .some
state. Thus, optional properties and methods, in contrast to optional editing , increase the nesting level of the returned option. An optional method, like a property, is "wrapped" in an optional completely - the .some
method is placed in the .some
value, and not just the return value:
@objc public protocol myOptionalProtocol { @objc optional var my_variable: Int { get } @objc optional var my_optionalVariableA: Int? { get } // : // @objc Int?, .. Int // @objc optional var my_optionalVariableB: UIView? { get } @objc optional func my_func() -> Int @objc optional func my_optionalResultfuncA() -> Int? // : // @objc Int?, .. Int // @objc optional func my_optionalResultfuncB() -> UIView? @objc optional init(value: Int) // : // optional } @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, myOptionalProtocol { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let protocolAdoption = self as myOptionalProtocol // Optional<Int> print("\(type(of: protocolAdoption.my_variable))") // Optional<Optional<UIView>> print("\(type(of: protocolAdoption.my_optionalVariableB))") // Optional<() -> Int> print("\(type(of: protocolAdoption.my_func))") // Optional<Int> print("\(type(of: protocolAdoption.my_func?()))") // Optional<() -> Optional<UIView>> print("\(type(of: protocolAdoption.my_optionalResultfuncB))") // Optional<UIView> print("\(type(of: protocolAdoption.my_optionalResultfuncB?()))") return true } }
@objc
- , , Swift Objective-C:
@objc
(.. );optional
- init
;@objc
— .Force Unwrapping , Force Unwrapping .none
.
Nil-Coalescing .some
- , .some
, , .none
. Nil-Coalescing , if else
, , ?
:
let optionalText: String? = tryExtractText() // let textA: String if optionalText != nil { textA = optionalText! } else { textA = "Extraction Error!" } // , let textB = (optionalText != nil) ? optionalText! : "Extraction Error!" // let textC = optionalText ?? "Extraction Error!"
.some
- . :
let optionalText: String?? = tryExtractOptionalText() let a = optionalText ?? Optional("Extraction Error!")
:
let wayA: Int? = doSomething() let wayB: Int? = doNothing() let defaultWay: Int = ignoreEverything() let whatDo = wayA ?? wayB ?? defaultWay
Optional Binding , .some
-, , ( ). Optional Binding if
, while
guard
.
Optional Binding , , .
, , nil
, nil
. if
, true , nil
false :
var my_optionalVariable: Int? = 42 // , my_variable "" .some- my_optionalVariable if let my_variable = my_optionalVariable { print("\(my_variable)") // 42 } my_optionalVariable = nil // , my_variable if let my_variable = my_optionalVariable { print("\(my_variable)") } else { print("Optional variable is nil!") // Optional variable is nil! }
, true , . - nil
.some
- . true .some
- "" ( Optional Binding ).
if
true , , false . , , .some
-, false ( .none
) . guard
:
let my_optionalVariable: Int? = extractOptionalValue() // let my_variableA: Int if let value = my_optionalVariable { my_variableA = value } else { return } print(my_variableA + 1) // guard let my_variableB = my_optionalVariable else { return } print(my_variableB + 1)
Swift (, ) as
. , (, ), as!
, as?
. Force Unwrapping , .. , nil
:
class Shape {} class Circle: Shape {} class Triangle: Shape {} let circle = Circle() let circleShape: Shape = Circle() let triangleShape: Shape = Triangle() circle as Shape // 42 as Float // circleShape as Circle // circleShape as! Circle // circle triangleShape as! Circle // circleShape as? Circle // Optional<Circle> triangleShape as? Circle // nil
, as?
, Optional Binding :
class Shape {} class Circle: Shape {} class Triangle: Shape {} let circleShape: Shape = Circle() let triangleShape: Shape = Triangle() // , if let circle = circleShape as? Circle { print("Cast success: \(type(of: circle))") // Cast success: (Circle #1) } else { print("Cast failure") } // , if let circle = triangleShape as? Circle { print("Cast success: \(type(of: circle))") } else { print("Cast failure") // Cast failure }
map
flatMap
Swift, Optional :
public enum Optional<Wrapped> : ExpressibleByNilLiteral { ... /// Evaluates the given closure when this `Optional` instance is not `nil`, /// passing the unwrapped value as a parameter. /// /// Use the `map` method with a closure that returns a nonoptional value. public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U? /// Evaluates the given closure when this `Optional` instance is not `nil`, /// passing the unwrapped value as a parameter. /// /// Use the `flatMap` method with a closure that returns an optional value. public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U? ... }
.some
- , . nil
, nil
. map
flatmap
-: flatMap
nil
(), map
:
let my_variable: Int? = 4 let my_squareVariable = my_variable.map { v in return v * v } print("\(my_squareVariable)") // Optional(16) let my_reciprocalVariable: Double? = my_variable.flatMap { v in if v == 0 { return nil } return 1.0 / Double(v) } print("\(my_reciprocalVariable)") // Optional(0.25)
map
flatmap
, if
guard
, , :
let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd MMM yyyy" let date: Date? = extractOptionalDate() // let dateStringA: String if date != nil { dateStringA = dateFormatter.string(from: date!) } else { dateStringA = "Unknown date" } // , let dateStringB = (date == nil ? nil : dateFormatter.string(from: date!)) ?? "Unknown date" // , ( map ) let dateStringC = date.map(dateFormatter.string) ?? "Unknown date"
, . , .some
- , map
flatmap
:
// Optional Binding func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) { let item = items[indexPath.row] } } // 3 : // 1) ; // 2) flatMap ; // 3) Optional Binding flatMap. func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let indexPath = (sender as? UITableViewCell).flatMap(tableView.indexPathForCell) { let item = items[indexPath.row] } }
func someThrowingFunction() throws -> Int { // ... } // let y: Int? do { y = try someThrowingFunction() } catch { y = nil } // let x = try? someThrowingFunction()
x
y
, , someThrowingFunction()
. , try?
, as?
. try!
, . , :
// , loadImage // photo ( x y) let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
Objective-C . nil
Objective-C , .. nil
. Swift nil
.none
, nil
, Xcode 6.3 Objective-C Swift . Xcode 6.3 Objective-C nullability annotations :
@interface myObject : NSObject @property (copy, readonly) NSArray * _Nullable myValuesA; @property (copy, readonly) NSString * _Nonnull myStringA; @property (copy, readonly, nullable) NSArray * myValuesB; @property (copy, readonly, nonnull) NSString * myStringB; @end
nullable
( _Nullable
), nonnull
( _Nonnull
), null_unspecified
null_resettable
. Nullability -a , . NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
. , Objective-C ( , , nil
nonnull ).
The annotation null_resettable
implies that the property setter may take nil
, but the property getter instead nil
returns some default value .
Objective-C Swift :
NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
() ;nonnull
_Nonnull
() ;nullable
_Nullable
;null_resettable
;null_unspecified
( ) .Swift Objective-C :
.none
, NSNull ;.some
, .some
-.!
, :
as!
;try!
.The unary operator of logical negation is !
not considered, since it refers to another context.
?
, :
as?
;nil
try?
.The ternary conditional operator is ?
not considered because it belongs to a different context.
??
:
, . ++ Java "", . "" , , "" . , .. , Cogito, ergo sum (. — ", "). , . Swift .
UPD: (by Alexander Zimin) init(nilLiteral: ())
:
var test = Optional<Int>(nilLiteral: ())
, Apple .
Source: https://habr.com/ru/post/338766/
All Articles