免费开源的iOS开发学习平台

Swift:20 可选链

可选链是一种可以在当前值可能为nil的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是nil,那么调用将返回nil。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为nil,整个调用链都会失败,即返回nil。

使用可选链代替强制解包

通过在想调用的属性、方法、或下标的可选值后面放一个问号(?),可以定义一个可选链。这一点很像在可选值后面放一个叹号(!)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制解包将会触发运行时错误。

为了反映可选链式调用可以在空值(nil)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回nil则说明调用失败。

特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是Int类型,则会变为Int?类型。

下面演示代码将展示可选链式调用和强制展开的不同。

class Player {
    var name:String = "player name"
}

class Game {
    var player : Player?
}

let game = Game()

//强制解包,将会引发运行时错误
//let name = game.player!.name

if let name = game.player?.name {
    print("Player name : \(name).")
} else {
    print("Unable get Player name.")
}
//打印:Unable get Player name.

game.player = Player()

if let name = game.player?.name {
    print("Player name : \(name).")
} else {
    print("Unable get Player name.")
}
//打印:Player name : player name.

可选链应用在类上

通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的类中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。

下面这段代码定义了四个模型类,演示了使用可选链访问属性、方法和下标。

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("The number of rooms is \(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 && street != nil {
            return "\(buildingNumber) \(street)"
        } else {
            return nil
        }
    }
}

//访问属性
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.")
}
// 打印:Unable to retrieve the number of rooms.

//设定属性的值
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress //赋值不会被执行

//访问方法
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.")
}
// 打印:It was not possible to print the number of rooms.

//访问下标
if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// 打印:Unable to retrieve the first room name.

//下标赋值
john.residence?[0] = Room(name: "Bathroom")

//下标和属性组合使用
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// 打印:The first room name is Living Room.

连接多层可选链式调用

可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。

也就是说:

  • 如果你访问的值不是可选的,可选链式调用将会返回可选值。
  • 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。

因此:

  • 通过可选链式调用访问一个Int值,将会返回Int?,无论使用了多少层可选链式调用。
  • 类似的,通过可选链式调用访问Int?值,依旧会返回Int?值,并不会返回Int??。

下面的例子尝试访问john中的residence属性中的address属性中的street属性。这里使用了两层可选链式调用,residence以及address都是可选值:

//john.residence?.address?.street的返回值也依然是String?,
//即使已经使用了两层可选链式调用。
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// 打印 “Unable to retrieve the address.”

在方法的可选返回值上进行可选链式调用

上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。

在下面的例子中,通过可选链式调用来调用Address的buildingIdentifier()方法。这个方法返回String?类型的值。如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
        if beginsWithThe {
            print("John's building identifier begins with \"The\".")
        } else {
            print("John's building identifier does not begin with \"The\".")
        }
}
// 打印:John's building identifier begins with "The".