[Swift learning] Swift programming tour-optional chain (21), swift tour
Optional chain Optional Chaining is a type where the current value may benil
To request and call properties, methods, and methods of the following standard. If the optional value has a value, the call is successful. If the optional value isnil
, Then the call will returnnil
. Multiple calls can be connected to form a call chain. If any of the nodes isnil
, The entire call chain will fail, that is, returnnil
.
By placing a question mark (?
), You can define an optional chain. This is like placing an exclamation mark (!
) To forcibly expand its value. The main difference between them is that when the optional value is null, the optional chained call will only fail to be called, but forcible expansion will trigger a runtime error.
To reflect the optional chained call values (nil
). No matter whether the property, method, or subscript returned value is an optional value, the returned result is an optional value. You can use this return value to determine whether your optional chained call is successful. If the call has a return value, the call is successful and the return value isnil
The call fails.
In particular, the returned results of the optional chained call have the same type as the original returned results, but are encapsulated into an optional value. For example, when an optional chained call is successful, if the original returned result of the attribute isInt
Type, it will becomeInt?
Type.
The following sections of Code explain the differences between optional chained call and force expansion.
First define two classesPerson
AndResidence
:
class Person { var residence: Residence?}class Residence { var numberOfRooms = 1}
Residence
There isInt
Type attributenumberOfRooms
The default value is1
.Person
Has an optionalresidence
Attribute, whose type isResidence?
.
If you create a newPerson
Instance because of itsresidence
The property is optional,john
Attribute will be initializednil
:
let john = Person()
If you use an exclamation mark (!
) Forcibly expand to obtain thisjohn
Ofresidence
AttributenumberOfRooms
Value.residence
No value that can be expanded:
Let roomCount = john. residence !. NumberOfRooms // this will cause a running error
john.residence
Notnil
Value, the above call will be successful, androomCount
SetInt
Number of rooms of the type. As mentioned above, whenresidence
Isnil
The above code will trigger a runtime error.
Optional chained call provides another type of accessnumberOfRooms
By using the question mark (?
) To replace the original exclamation mark (!
):
If let roomCount = john. residence ?. NumberOfRooms {print ("John's residence has \ (roomCount) room (s ). ")} else {print (" Unable to retrieve the number of rooms. ")} // print" Unable to retrieve the number of rooms."
Inresidence
After a question mark is added, Swift willresidence
Notnil
AccessnumberOfRooms
.
Because accessnumberOfRooms
Possible failure. Optional chained call will returnInt?
Type, or "optional"Int
". As shown in the preceding example, whenresidence
Isnil
OptionalInt
Thenil
Indicates that the instance cannot be accessed.numberOfRooms
. Optional when the access is successfulInt
The value is expanded by optional binding and assigned to non-optionalroomCount
Constant.
Note that even ifnumberOfRooms
OptionalInt
This is also true. The optional chained call meansnumberOfRooms
ReturnsInt?
InsteadInt
.
You canResidence
Instance assignedjohn.residence
In this way, it is no longernil
Now:
john.residence = Residence()
john.residence
Now contains an actualResidence
Instance, insteadnil
. If you try to use the previous optional chain call for accessnumberOfRooms
, Which now returns1
OfInt?
Type value:
If let roomCount = john. residence ?. NumberOfRooms {print ("John's residence has \ (roomCount) room (s ). ")} else {print (" Unable to retrieve the number of rooms. ")} // print" John's residence has 1 room (s )."
Define model classes for optional chained calls
You can call attributes, methods, and subscripts of multiple layers by using optional chained calls. In this way, you can access the sub-attributes in a complex model and determine whether the Sub-attributes, methods, or subscript can be accessed.
The following code defines four model classes, including multiple optional chain calls. For conveniencePerson
AndResidence
AddedRoom
Class andAddress
Class and related attributes, methods, and subscripts.
Person
Class:
class Person { var residence: Residence?}
Residence
Class
class Residence { var rooms = [Room]() var numberOfRooms: Int { return rooms.count } subscript(i: Int) -> Room { get { return rooms[i] } set { rooms[i] = newValue } } func printNumberOfRooms() { print("The number of rooms is \(numberOfRooms)") } var address: Address?}
NowResidence
With a storageRoom
Array of instances,numberOfRooms
Attributes are implemented as computing attributes, rather than storage attributes.numberOfRooms
Attribute is returned simplyrooms
Arraycount
Attribute Value.
Residence
Also provides accessrooms
Array shortcut, that is, provide accessible subscriptrooms
Array.
In addition,Residence
Also providesprintNumberOfRooms()
Method. The function of this method is to printnumberOfRooms
.
Finally,Residence
An optional attribute is also defined.address
, Whose type isAddress?
.Address
The class definition is described below.
Room
A class is a simple class, and its instance is stored inrooms
Array. This class contains only one attribute.name
And an initialization function to set this attribute to the appropriate room Name:
class Room { let name: String init(name: String) { self.name = name }}
The last class isAddress
This class has threeString?
Type.buildingName
AndbuildingNumber
Property indicates the name and number of a building, and the third property.street
Name of the street where the building is located:
class Address { var buildingName: String? var buildingNumber: String? var street: String? func buildingIdentifier() -> String? { if buildingName != nil { return buildingName } else if buildingNumber != nil && street != nil { return "\(buildingNumber) \(street)" } else { return nil } }}
Address
Class providesbuildingIdentifier()
Method. The returned value isString?
. IfbuildingName
Return if there is a valuebuildingName
. Or, ifbuildingNumber
Andstreet
Returns if all values exist.buildingNumber
. Otherwise, returnnil
.
Access attributes through optional chained calls
You can use an optional chained call to access its attributes on an optional value and determine whether the access is successful.
The following code createsPerson
Instance, and then try to accessnumberOfRooms
Attribute:
Let john = Person () if let roomCount = john. residence ?. NumberOfRooms {print ("John's residence has \ (roomCount) room (s ). ")} else {print (" Unable to retrieve the number of rooms. ")} // print" Unable to retrieve the number of rooms."
Becausejohn.residence
Isnil
So this optional chain call will still fail as before.
You can also set the attribute value through optional chained calls:
let someAddress = Address()someAddress.buildingNumber = "29"someAddress.street = "Acacia Road"john.residence?.address = someAddress
In this examplejohn.residence
To setaddress
Attribute will also fail, becausejohn.residence
Currentlynil
.
The assignment process in the above Code is part of the optional chained call, which means that the Code on the right of the equal sign will not be executed when the optional chained call fails. For the above Code, it is difficult to verify this, because assigning a constant like this has no side effects. The following code completes the same thing, but it uses a function to createAddress
Instance. This Function prints "Function was called" before the return, which enables you to verify whether the code on the right of the equal sign is executed.
func createAddress() -> Address { print("Function was called.") let someAddress = Address() someAddress.buildingNumber = "29" someAddress.street = "Acacia Road" return someAddress}john.residence?.address = createAddress()
No messages are printed.createAddress()
The function is not executed.
Call a method using an optional chain
You can call a method through an optional chained call and determine whether the call is successful, even if the method does not return a value.
Residence
ClassprintNumberOfRooms()
Method to print the currentnumberOfRooms
Value, as shown below:
func printNumberOfRooms() { print("The number of rooms is \(numberOfRooms)")}
This method does not return values. However, methods without return values have implicit return types.Void
. This means that methods without return values will also return()
Or empty tuples.
If you call this method through optional chained calls on optional values, the return type of this method will beVoid?
InsteadVoid
Because the returned values obtained through optional chained calls are optional. In this way, we can useif
Statement to determine whether the call can be successfulprintNumberOfRooms()
Method, even if the method itself does not define the return value. Determine whether the returned value isnil
You can determine whether the call is successful:
If john. residence ?. PrintNumberOfRooms ()! = Nil {print ("It was possible to print the number of rooms. ")} else {print (" It was not possible to print the number of rooms. ")} // print" It was not possible to print the number of rooms."
Similarly, you can determine whether the attribute value is successfully assigned through an optional chained call. In the above, we tryjohn.residence
Inaddress
Attribute assignment, even ifresidence
Isnil
. An optional chained call to assign a value to an attribute will returnVoid?
To determine whether the returned value isnil
You can see whether the assignment is successful:
If (john. residence ?. Address = someAddress )! = Nil {print ("It was possible to set the address. ")} else {print (" It was not possible to set the address. ")} // print" It was not possible to set the address."
Access subscript through optional chain calls
Through optional chained calls, we can access the subscript on an optional value and determine whether the subscript call is successful.
Note:
When you access the lower mark of an optional value through an optional chained call, you should put the question mark before the subscript square brackets instead of the following. The question mark of an optional chained call is generally followed by an optional expression.
The following example uses subscript to accessjohn.residence
Attribute StorageResidence
Instancerooms
The name of the first room in the array, becausejohn.residence
Isnil
So the subscript call failed:
If let firstRoomName = john. residence? [0]. name {print ("The first room name is \ (firstRoomName ). ")} else {print (" Unable to retrieve the first room name. ")} // print" Unable to retrieve the first room name."
In this example, the question mark is placed directly injohn.residence
And in front of square brackets, becausejohn.residence
Is an optional value.
Similarly, you can assign values by subscript and using optional chained calls:
john.residence?[0] = Room(name: "Bathroom")
This assignment will also fail becauseresidence
Currentlynil
.
If you createResidence
Androoms
Add someRoom
InstanceResidence
Instance assignedjohn.residence
Then you can access the elements in the array through optional links and subscript:
Let johnsHouse = Residence () johnsHouse. rooms. append (Room (name: "Living Room") johnsHouse. rooms. append (Room (name: "Kitchen") john. residence = johnsHouseif let firstRoomName = john. residence? [0]. name {print ("The first room name is \ (firstRoomName ). ")} else {print (" Unable to retrieve the first room name. ")} // print" The first room name is Living Room."
Access optional subscript
If the subscript returns an optional type value, for example, in SwiftDictionary
The subscript of A type key. You can place a question mark after the end of the lower mark to perform optional chained calls on its optional return values:
Var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81] testScores ["Dave"]? [0] = 91 testScores ["Bev"]? [0] ++ testScores ["Brian"]? [0] = 72 // "Dave" array is now [91, 82, 84], and "Bev" array is now [80, 94, 81]
The preceding example definestestScores
Array, contains two key-value pairsString
Type key ing TOInt
Value array. This example uses optional chain call"Dave"
The first element in the array is set91
"Bev"
The first element of the array.+1
And then try"Brian"
The first element in the array is set72
. The first two calls are successful becausetestScores
The dictionary contains"Dave"
And"Bev"
These two keys. HowevertestScores
No in the dictionary"Brian"
This key, so the third call failed.
Multi-layer optional chain call
You can connect multiple optional chain calls to access attributes, methods, and subscripts at a deeper model level. However, multi-layer optional chained calls do not increase the optional hierarchy of returned values.
That is to say:
- If the accessed value is not optional, the optional chained call will return an optional value.
- If the value you access is optional, the optional chained call does not make the optional return value "more optional ".
Therefore:
- Access
Int
Value.Int?
, No matter how many layers of optional chained calls are used.
- Similarly, access through optional chain calls
Int?
Value.Int?
Value, and does not returnInt??
.
The following example attempts to accessjohn
Inresidence
Attributeaddress
Attributestreet
Attribute. Two optional chain calls are used here,residence
Andaddress
Optional values:
If let johnsStreet = john. residence ?. Address ?. Street {print ("John's street name is \ (johnsStreet ). ")} else {print (" Unable to retrieve the address. ")} // print" Unable to retrieve the address."
john.residence
Now include a validResidence
Instance. However,john.residence.address
The current value isnil
. Thereforejohn.residence?.address?.street
Will fail.
Note that, in the above example,street
IsString?
.john.residence?.address?.street
The return value is stillString?
, Even if two optional chain calls are used.
Ifjohn.residence.address
Assign a valueAddress
Andaddress
Instreet
Set a valid value for the property so that we can access it through optional chained calls.street
Attribute:
Let johnsAddress = Address () johnsAddress. buildingName = "The Larches" johnsAddress. street = "Laurel Street" john. residence ?. Address = johnsAddressif let johnsStreet = john. residence ?. Address ?. Street {print ("John's street name is \ (johnsStreet ). ")} else {print (" Unable to retrieve the address. ")} // print" John's street name is Laurel Street."
In the preceding examplejohn.residence
Contains a validResidence
Instancejohn.residence
Ofaddress
Attribute assignment is successful.
Perform optional chained calls on optional return values of methods
The preceding example shows how to obtain the attribute value of an optional value through an optional chained call. We can also call methods by optional chained calls on an optional value, and continue to perform optional chained calls on the optional return values of methods as needed.
In the following example, callAddress
OfbuildingIdentifier()
Method. This method returnsString?
Type value. As mentioned above, the final return value will still beString?
Type:
If let buildingIdentifier = john. residence ?. Address ?. BuildingIdentifier () {print ("John's building identifier is \ (buildingIdentifier).")} // print "John's building identifier is The Larches ."
If you want to perform an optional chained call on the return value of the method, add a question mark after the parentheses of the method:
If let beginsWithThe = john. residence ?. Address ?. BuildingIdentifier ()?. HasPrefix ("The") {if beginsWithThe {print ("John's building identifier begins with \" \". ")} else {print (" John's building identifier does not begin with \ "\". ") }}// print" John's building identifier begins with ""."
Note:
In the above example, the question mark is added after the parentheses of the method because you needbuildingIdentifier()
The optional return value of the method, rather than the method itself.