草庐IT

Swift 可选链

runoob 2023-04-07 原文

Swift 可选链

可选链(Optional Chaining)是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。

可选链返回两个值:

  • 如果目标有值,调用就会成功,返回该值

  • 如果目标为nil,调用将返回nil

多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效。


可选链可替代强制解析

通过在属性、方法、或下标脚本的可选值后面放一个问号(?),即可定义一个可选链。

可选链 '?' 感叹号(!)强制展开方法,属性,下标脚本可选链
? 放置于可选值后来调用方法,属性,下标脚本 ! 放置于可选值后来调用方法,属性,下标脚本来强制展开值
当可选为 nil 输出比较友好的错误信息 当可选为 nil 时强制展开执行错误

使用感叹号(!)可选链实例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

//将导致运行时错误
let roomCount = john.residence!.numberOfRooms

以上程序执行输出结果为:

fatal error: unexpectedly found nil while unwrapping an Optional value

想使用感叹号(!)强制解析获得这个人residence属性numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供解析的residence值。

使用问号(?)可选链实例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
    print("John 的房间号为 \(roomCount)。")
} else {
    print("不能查看房间号")
}

以上程序执行输出结果为:

不能查看房间号

因为这种尝试获得numberOfRooms的操作有可能失败,可选链会返回Int?类型值,或者称作"可选Int"。当residence是空的时候(上例),选择Int将会为空,因此会出现无法访问numberOfRooms的情况。

要注意的是,即使numberOfRooms是非可选Int(Int?)时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int。


为可选链定义模型类

你可以使用可选链来多层调用属性,方法,和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

实例

定义了四个模型类,其中包括多层可选链:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

通过可选链调用方法

你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()


if ((john.residence?.printNumberOfRooms()) != nil) {
    print("输出房间号")
} else {
    print("无法输出房间号")
}

以上程序执行输出结果为:

无法输出房间号

使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过可选链调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil。


使用可选链调用下标脚本

你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功,然而,你不能通过可选链来设置下标脚本。

实例1

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()
if let firstRoomName = john.residence?[0].name {
    print("第一个房间名 \(firstRoomName).")
} else {
    print("无法检索到房间")
}

以上程序执行输出结果为:

无法检索到房间

在下标脚本调用中可选链的问号直接跟在 john.residence 的后面,在下标脚本括号的前面,因为 john.residence 是可选链试图获得的可选值。

实例2

实例中创建一个 Residence 实例给 john.residence,且在他的 rooms 数组中有一个或多个 Room 实例,那么你可以使用可选链通过 Residence 下标脚本来获取在 rooms 数组中的实例了:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John 所在的街道是 \(johnsStreet)。")
} else {
    print("无法检索到地址。 ")
}

以上程序执行输出结果为:

John 所在的街道是 Laurel Street。

通过可选链接调用来访问下标

通过可选链接调用,我们可以用下标来对可选值进行读取或写入,并且判断下标调用是否成功。

实例

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间名为\(firstRoomName)")
} else {
    print("无法检索到房间")
}

以上程序执行输出结果为:

第一个房间名为客厅

访问可选类型的下标

如果下标返回可空类型值,比如Swift中Dictionary的key下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上面的例子中定义了一个testScores数组,包含了两个键值对, 把String类型的key映射到一个整形数组。

这个例子用可选链接调用把"Dave"数组中第一个元素设为91,把"Bev"数组的第一个元素+1,然后尝试把"Brian"数组中的第一个元素设为72。

前两个调用是成功的,因为这两个key存在。但是key"Brian"在字典中不存在,所以第三个调用失败。


连接多层链接

你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和下标脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。

如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int?。 相似的,如果你试图通过可选链获得Int?值,不论使用了多少层链接返回的总是Int?。

实例1

下面的例子试图获取john的residence属性里的address的street属性。这里使用了两层可选链来联系residence和address属性,它们两者都是可选类型:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

if let johnsStreet = john.residence?.address?.street {
    print("John 的地址为 \(johnsStreet).")
} else {
    print("不能检索地址")
}

以上程序执行输出结果为:

不能检索地址

实例2

如果你为Address设定一个实例来作为john.residence.address的值,并为address的street属性设定一个实际值,你可以通过多层可选链来得到这个属性值。

class Person {
   var residence: Residence?
}

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("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

class Room {
    let name: String
    init(name: String) { self.name = name }
}

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}
let john = Person()
john.residence?[0] = Room(name: "浴室")

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间是\(firstRoomName)")
} else {
    print("无法检索房间")
}

以上实例输出结果为:

第一个房间是客厅

对返回可选值的函数进行链接

我们还可以通过可选链接来调用返回可空值的方法,并且可以继续对可选值进行链接。

实例

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

if john.residence?.printNumberOfRooms() != nil {
    print("指定了房间号)")
}  else {
    print("未指定房间号")
}

以上程序执行输出结果为:

未指定房间号

有关Swift 可选链的更多相关文章

  1. ruby - 如何在 Cucumber 步骤定义中使单词可选? - 2

    我在下面有一个步骤定义,它执行我想要它执行的操作,即它根据“PAGES”哈希的“page”元素检查页面的url。Then(/^Ishould(still)?beatthe"(.*)"page$/)do|still,page|BROWSER.url.should==PAGES[page]end步骤定义用于两者我应该在...页面我应该还在...页面但是,我不需要将“still”传递到block中。我只需要它是可选的以匹配步骤但不传递到block中。我该怎么做?谢谢。 最佳答案 您想将“静止”组标记为非捕获。这是通过使用?:启动组来完成的

  2. ruby - 如何确定是否在 Ruby 中传递了可选参数 - 2

    如果我在Ruby中有以下方法:deffoo(arg1,arg2="bar")putsarg1putsarg2end有没有办法确定用户是否在方法中为arg2传递了一个值?显然,我可以将ifarg2=="bar"添加到方法中,但这并没有捕捉到用户自己手动传入"bar"的情况。当然,我可以将默认值设置为任何用户都不会传入的内容,但这样很快就会变得非常丑陋。那里有什么优雅的东西吗? 最佳答案 deffoo(arg1,arg2=(arg2_not_passed=true;"bar"))putsarg1putsarg2puts'arg2wasp

  3. ruby - 我如何记录提供 block 是可选的方法? - 2

    我有一个这样记录的函数:###Searchesforstreetnamesinthelocaladdressdatabase.Returnsalist#ofstrings,orinvokestheblockforeachresult.##@param[String,Hash]query##Canbe:##-Asearchstringwithoptinalwildcards.Examples:#-"Bærumsv*"#-"Fornebuve_en"##@param[Integer]limit##Limitstheamountofresults.See{#search_street_add

  4. ruby - 是否可以在 Ruby 中定义带有可选参数的 block ? - 2

    我正在尝试动态定义调用另一个采用选项参数的函数的函数:classMyClass["hour","minute","second"].eachdo|interval|define_method"get_#{interval}"do|args|some_helper(interval,args)endenddefsome_helper(interval,options={})#Dosomething,withargumentsendend我希望能够以这两种方式调用MyClass的不同方法(有和没有可选参数):mc=MyClass.newmc.get_minute(:first_option

  5. Rubygems 可选依赖项 - 2

    我如何向ruby​​gems添加可选依赖项,因为它不支持它?用例:我有一个依赖于Backports、Extlib、Facets或ActiveSupport的库。在过去,我只是没有添加任何依赖项,因为人们可以选择(阅读:它可以很好地与他们使用的任何东西一起使用),但是人们提示很多库不能开箱即用,或者他们会相信它取决于ActiveSupport(在某些情况下对某些人来说是不行的)。所以目前它依赖于Backports,因为它是最小的。尽管这意味着人们会在安装我的库时安装Backports,即使他们可能不会在运行时使用它。我想根据环境依赖不同的gem(例如MRI/Rubinius上的Johns

  6. ruby - 如何确定是否在 ruby​​ 方法中给出了可选参数 - 2

    我有一个带有可选参数的方法。我如何确定是否给出了Argument?我想出了以下解决方案。我问这个问题是因为我对其中任何一个都不完全满意。还有更好的吗?nil作为默认值defm(a=nil)ifa.nil?...endend这个的缺点是,无法确定是没有参数还是nil被给出。自定义NoArgument作为默认值classNoArgumentenddefm(a=NoArgument.new)ifNoArgument===a...endend是否给定nil是可以判断的,但是NoArgument的实例也存在同样的问题。评估省略号的大小defm(*a)raiseArgumentErrorifm.s

  7. Ruby:如何获取可选 proc 参数的默认值 - 2

    假设我有一个像这样的proc/lambda/block/method/etc:2.1.2:075>procedure=Proc.new{|a,b=2,*c,&d|42}=>#我知道我可以通过以下方式找出参数的名称:2.1.2:080>procedure.parameters=>[[:opt,:a],[:opt,:b],[:rest,:c],[:block,:d]]但是,如果给定的可选参数未给定,我该如何获取该可选参数的值呢?附言:是的。我知道这在here之前已经被问过/回答过,但之前的解决方案需要使用merbgem,这实际上有点误导。merb本身依赖于methoparagem(除非您使

  8. ruby - 如何检查 ruby​​ 方法中可选参数的默认值是多少? - 2

    给定一个类,classMyClassdefindex(arg1,arg2="hello")endend是否可以通过Class#instance_method等方法获取arg2的默认值? 最佳答案 似乎我们可以检查方法参数值的唯一方法是访问方法的binding。使用Tracepoint类,我们可以获取这样一个绑定(bind)对象,然后检查所有optional参数的值。我们需要确保只使用必需的参数调用所需的方法,以便为默认参数分配默认值。下面是我尝试这样做的-它适用于实例方法和类方法。为了调用实例方法,我们需要实例化类-如果构造函数需要

  9. ruby - sinatra路由中的几个可选参数 - 2

    我需要Sinatra路由以下列方式运行:GET/list/20/10#Get20itemswithoffset10GET/list/20#Get20itemswithdefaultoffsetGET/list#Getdefaultnumberofitemswithdefaultoffset我明白,我可能会将值作为查询传递:GET/list?limit=20&offset=10但我想如上所述传递它们。我很确定有一种方法可以向Sinatra/Padrino解释我想做什么,但我目前完全被困住了。我试过:get:list,:map=>'/list',:with=>[:limit,:offset

  10. ruby - splat 参数后的可选参数 - 2

    这是我的程序:defcalculate(*numbers,options={})add(numbers)ifoptions[:add]subtract(numbers)ifoptions[:add]==falseenddefadd(*numbers)numbers.reduce(:+)enddefsubtract(*numbers)numbers.reduce(:-)endpcalculate(1,2)在第一行,它在提示tests.rb:1:syntaxerror,unexpected'=',expecting')'defcalculate(*numbers,options={})__

随机推荐