Objective-C與Swift語言對于可選nil的不同理解:
Objective-C中的nil:表示缺少一個合法的對象,是指向不存在對象的指針,對結構體、枚舉等類型不起作用(會返回NSNotFound)
Swift中的nil:表示任意類型的值缺失,是一個確定的值,要么是該類型的一個值要么什么都沒有(即為nil)
一、申明可選常量或變量(非可選型不可以設置為nil,否則會報錯)
let status: Int? = 1 // 申明可選Int類型的常量,初始值為1
var defaultAddress: String? = "江蘇南京" // 申明可選String類型的變量,初始值為"江蘇南京"
var student: Person? // 申明可選Person(自定義的類)的變量,初始值為nil
二、使用"!"強制解析獲取可選類型的值(不建議直接使用)
var defaultAddress: String? = "江蘇南京"
if defaultAddress != nil { // !=或==可以用來判斷是否為nil
print("您的地址是\(defaultAddress!)") // 使用!強制解析
} else {
print("對不起,您不存在地址信息")
}
var student: Person?
print("學生為\(student!)") // XCode會提示運行錯誤,因為student初始值為nil,強制解析不行
三、使用可選綁定獲取可選類型的值(建議的用法)
var defaultAddress: String? = "江蘇南京"
if let address = defaultAddress { // 如果defaultAddress有值或類型轉換成功,則將值賦值給address直接使用
print("您的地址是\(address)") // 使用address代替defaultAddress,且不需要加!強制解析
} else {
print("對不起,您不存在地址信息")
}
四、隱式解析可選類型(用于申明時肯定有初始值,但后面可能為nil)
var mobileNumber: Int64! = 13912345678 // 第一次申明有初始值
//var mobileNumber: Int64! = nil // 這樣也不會崩潰
print("您的電話號碼是\(mobileNumber)") // 不需要使用!強制解析
// 打印內容:**您的電話號碼是****Optional(13912345678)**
// 還是不建議直接強制解析,因為實際項目中可能中間已經對該值做了改變,若為nil則會運行錯誤導致APP崩潰
if let number = mobileNumber { // 建議的做法
print("您的電話號碼是\(number)")
// 打印內容:**您的電話號碼是****13912345678**
} else {
print("您沒有記錄電話號碼")
}
五、空合運算符(用于判斷變量或常量是否為nil)
// 空合運算符:a ?? b 判斷a是否為nil,若a不為nil對a解封,否則返回b的值
var status: Int? // 申明可選Int類型的變量status,初始值為nil
status ?? 0 // 因為status為nil,則返回0
// ?? 即為以下if else的縮寫
func testOption() -> Int {
let status: Int? = 1
if status == nil {
return 0
} else {
return status!
}
}
六、函數/方法返回類型為可選類型
A:返回值為可選類型的值(如Int?、String?、(Int, String)?、[Int]?、[Int: String]?等)
func returnPossibleValue(value: Bool) -> String? { // 返回類型為可選String類型
if value {
return "返回類型是可選類型值" // 如果為真,返回Int類型的值1
} else {
return nil // 如果為假,返回nil
}
}
let possibleValue = returnPossibleValue(value: true) // 要用可選綁定判斷再使用,因為possibleValue為String?可選類型
if let value = possibleValue {
print(value)
} else {
print("none value")
}
B:返回值為可選類型的類(如URL?、自定義Person?等)
class SomeClass {
var someValue: Int
init?(someValue: Int) { // 可失敗構造器
if someValue == 1 { return nil }
self.someValue = someValue
}
}
func returnPossibleClass(value: Bool) -> SomeClass? { // 返回的類實例可能為nil
if value {
return SomeClass(someValue: 1) // 返回的為nil
} else {
return SomeClass(someValue: 2) // 返回的SomeClass?實例,不為nil
}
}
C:返回值為可選類型的閉包(如(()-> (void))? )
func returnOptionalFunc(value: Bool) -> (() -> (Void))? { // 返回類型為可選類型的閉包
if value {
return { () in
print("返回類型是可選類型閉包")
}
} else {
return nil
}
}
let possibleFunc = returnOptionalFunc(value: true) // 要用可選綁定判斷再使用,因為possibleFunc 為可選類型的閉包,類型為() -> (Void)
if let aFunc = possibleFunc {
print(aFunc()) // 注意增加()調用閉包,因為沒有參數則是空括號
} else {
print("none func")
}
七、可選類型在類或結構體中的運用
A:可選類型在類中的運用
class PossibleClass {
var someValue: Int
var possibleValue: String? // 可選存儲屬性,默認值為nil
init(someValue: Int) { // 構造方法中可以不對possibleValue屬性初始化
self.someValue = someValue
}
}
let someClass = PossibleClass(someValue: 4) // 實例化對象時不需要
注意:類中所有屬性都需要有默認值。屬性可以在申明時賦予初始值,也可以在構造方法中賦予初始值;子類繼承父類時,必須先給自身屬性先初始化后再繼承父類構造方法初始化。一般的,出于安全的因素,子類的屬性都賦予初始值或直接定義為可選類型。
B:可選類型在結構體中的運用
struct PossibleStruct {
var someValue: Int
var possibleValue: String? // 可選存儲屬性,默認值為nil
// 結構體中可以自定義一個init構造器對屬性初始化,也可以不自定義
}
let someStruct = PossibleStruct(someValue: 4, possibleValue: nil)
八、可選類型在構造器中的運用
概念:可失敗構造器是 一個類、結構體或枚舉類型的對象,在構造過程中有可能失敗;這里所指的“失敗”是指,如給構造器傳入無效的參數值,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。
A:結構體的可失敗構造器
struct PossibleStructInit {
let someValue: String
init?(someValue: String) { // 可失敗構造器
if someValue.isEmpty { return nil } // 如果實例化為空串,則返回nil(即實例化失敗)
self.someValue = someValue
}
}
let oneStruct = PossibleStructInit(someValue: "abc") // abc不是空串,oneStruct為PossibleStructInit?可選類型
if let one = oneStruct { // 使用if let可選綁定判斷
print(one.someValue)
} else {
print("none value")
}
// 結果打印 abc
let twoStruct = PossibleStructInit(someValue: "") // 傳參為空串
if let two = twoStruct {
print(two.someValue)
} else {
print("none value")
}
// 結果打印 none value
B:枚舉的可失敗構造器
enum PossibleEnmuInit {
case East, West, South, North // 不帶原始值,帶原始值也可以使用可失敗構造器
init?(director: Character) {
switch director {
case "E":
self = .East
case "W":
self = .West
case "S":
self = .South
case "N":
self = .North
default:
return nil // 如果實例化時不是E/W/S/N字符,則返回nil(即實例化失敗)
}
}
}
let oneEnmu = PossibleEnmuInit(director: "E") // 傳值E,返回PossibleEnmuInit?可選類型
if let one = oneEnmu { // 使用可選綁定判斷
print(one)
} else {
print("none value")
}
// 結果打印 East
let twoEnmu = PossibleEnmuInit(director: "A") // 傳值A,返回PossibleEnmuInit?可選類型
if let two = twoEnmu { // 使用可選綁定判斷
print(two)
} else {
print("none value")
}
// 結果打印 none value
C:類的可失敗構造器
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil } // 實例化時如果quantity小于1,則立即終止構造過程
self.quantity = quantity
super.init(name: name) // 如果name為空串,則立即終止構造過程
}
}
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2”
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// 打印 "Unable to initialize zero shirts”
九、可選類型在可選鏈中的運用
概念:可選鏈為一種可以在當前值可能為nil的可選值上請求和調用屬性、方法及下標的方法。如果可選值有值,那么調用就會成功;如果可選值是nil,那么調用將返回nil。多個調用可以連接在一起形成一個調用鏈,如果其中任何一個節點為nil,整個調用鏈都會失敗,即返回nil。
class Person {
var name: String
var room: Room? // 不是每個人都買房,定義為可選類型
init(name: String) {
self.name = name
}
}
class Room {
var roomAddr: String
var roomArea: Int
init(roomAddr: String, roomArea: Int) {
self.roomAddr = roomAddr
self.roomArea = roomArea
}
}
let xiaoming = Person(name: "xiaoming") // 此時xiaoming實例中屬性:name為"xiaoming",room為nil
if let address = xiaoming.room?.roomAddr, let area = xiaoming.room?.roomArea { // 使用?可選鏈式調用,如果含有值則返回該值(不需要手動強制解析),如果沒有值則返回nil
print("\(xiaoming.name)的房子地址在:\(address), 面積:\(area)")
} else {
print("\(xiaoming.name)沒有房子")
}
// 打印:xiaoming沒有房子
xiaoming.room = Room(roomAddr: "南京雨花臺區", roomArea: 95) // 定義xiaoming實例的room屬性
// 再次調用以上可選綁定,打印 xiaoming的房子地址在:南京雨花臺區, 面積:95
十、可選類型在錯誤處理中的運用
概念:錯誤處理即為響應錯誤一級從錯誤中恢復的過程
A:一般的錯誤處理寫法(一般使用于需要對不同的返回結果較為清晰的捕捉并做相應的處理)
// 操作錯誤處理枚舉
enum OperationError: Error {
case ErrorOne
case ErrorTwo(String) // 帶關聯值的枚舉屬性
case ErrorOthers
}
// 錯誤處理拋出
func throwDriver(num: Int) throws {
if num == 1 {
throw OperationError.ErrorOne
} else if num == 2 {
throw OperationError.ErrorTwo("數據類型錯誤")
} else {
throw OperationError.ErrorOthers
}
}
do {
print("使用do-catch捕獲錯誤")
try throwDriver(num: 2) // 使用try嘗試請求
print("未捕獲到錯誤")
} catch OperationError.ErrorOne { // catch去捕捉是否又throw出來的錯誤
print("捕捉到錯誤:ErrorOne")
} catch OperationError.ErrorTwo(let message) { // 可以使用let 獲取枚舉關聯值
print("捕捉到錯誤:ErrorTwo" + message)
} catch OperationError.ErrorOthers {
print("捕捉到錯誤:ErrorOthers")
}
// 打印結果如下:
// 使用do-catch捕獲錯誤
// 捕捉到錯誤:ErrorTwo數據類型錯誤
B:錯誤處理(try!)(不建議使用,可能會導致App崩潰)
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
// 上述語句中在執行loadImage方法時如果執行失敗,使用try!來禁用錯誤傳遞,會有運行錯誤導致App崩潰
C:錯誤處理(try?)(一般使用于不需要對錯誤進行過多的處理)
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction() // x可能正常返回一個Int類型的值也有可能拋出一個錯誤異常,使用時對x用if let可選綁定判斷
十一、可選類型在類型轉換中的運用
類型轉換在向上轉型時一般不會出現失敗,例如從子類向父類轉型,直接用as即可
類型轉換在向下轉型時可能會出現失敗,例如從父類向子類轉型,要使用as?轉型,使用時需要可選綁定后再用值得注意的是:從Any轉成Int為向下轉型,Swift從安全因素考慮,會直接返回nil,所以在日常項目中若遇到從父類向子類轉型時,一定要使用可選綁定,如以下代碼:
class SuperClass {
// 這是一個父類
}
class SubClass: SuperClass {
// 這是一個子類
}
let superClass = SuperClass()
if let someClass = superClass as? SubClass {
// 如果轉換成功,則用someClass使用即可
} else {
// 轉換失敗
}