📜 ⬆️ ⬇️

Options in Swift

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.


What are Optionals?


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.


Why do we need Optionals when there is a nil check?


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 .


Note

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 .


How it works?


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.


Note

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 .


Read more

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 

Idioms use


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 .


Implicit unwrapping


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,:



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


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:



 //    :   —  `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( ... ) 

Note

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:



Force Unwrapping , Force Unwrapping .none .


Nil-Coalescing


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


Optional Binding , .some -, , ( ). Optional Binding if , while guard .


Optional Binding , , .


Swift return () . = , == .


, , 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


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] } } 


Swift , — nil . try? ( ):


 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


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_resettableimplies that the property setter may take nil, but the property getter instead nilreturns some default value .


Objective-C Swift :



Swift Objective-C :



,


! , :



The unary operator of logical negation is !not considered, since it refers to another context.


? , :



The ternary conditional operator is ?not considered because it belongs to a different context.


?? :



Conclusion


— . , . , null , .


, . ++ Java "", . "" , , "" . , .. , Cogito, ergo sum (. — ", "). , . Swift .


Additional materials



UPD: (by Alexander Zimin) init(nilLiteral: ()) :


 var test = Optional<Int>(nilLiteral: ()) 

, Apple .


')

Source: https://habr.com/ru/post/338766/


All Articles