已經有好幾我的跟我抱怨過爲何 swift 裏面有那麼多問號(?
)還有歎號(!
)了。偏偏哈, 在剛剛開始寫 swift 的時候, 我也面臨着這種問題。git
昨天一個朋友發了我一行代碼, 讓我看看應該怎麼寫:程序員
let pageiid = (self.pageid?.intValue)! + 1
複製代碼
這段代碼看起來很操蛋, 可是糟心的是, 在剛剛寫 swift 的時候, 我寫過更噁心的代碼。github
既然你們在剛剛開始寫 swift 的時候都遇到了這個問題。今天就來看看, 這樣的代碼應該怎樣寫才能讓咱們更爽。swift
咱們都知道, 有問號(?
)和歎號(!
)的緣由是什麼?—Optional安全
相信準備要試試 swift 的人,或多或少都看到過, 或者據說過這是 swift 相較於 oc 很大的區別。在我看來,除了語法上的變化之外, swift 和 oc 最大的區別就在 於optional
了。爲了照顧到實在是太新的新手(畢竟我本身也是新手)。仍是簡單的講講這個東西吧!閉包
這一點就沒什麼可說的,在 swift 中 Optional 其實是一個枚舉。若是要本身實現一個相似的東西的話, 核心的代碼應該是這樣的:app
// 這段代碼不重要
public enum SYOptional<Wrapped> {
case none
case some(Wrapped)
}
複製代碼
這段代碼只是要告訴你, 這個枚舉只有兩個 case, 一個是 .none
表明這個 optional 是沒有值的, 也就是說他是 nil。另一個值 .some
表明這個 optional 是有值的。優化
蘋果爲何要引入 optional 這個概念, 在這個地方就不打算贅述了。看下面一段話:ui
「Optional 能夠說是 Swift 的一大特點,它徹底解決了 「有」 和 「無」 這兩個困擾了 Objective-C 許久的哲學概念,也使得代碼安全性獲得了很大的增長。」編碼
摘錄來自: 王巍 (onevcat). 「Swifter - Swift 必備 Tips (第四版)」。 iBooks.
我的認爲 optional 確實是 swift 中很是好的新特性了。
接下來, 咱們看看 Optional 的那幾個操做符號: ??
/ !
/?
這些無非就是一些 swift 中的語法糖而已。具體什麼意思,咱們來往下看。
在聲明一個變量或者屬性的時候:
var optionalString: String?
複製代碼
最後的?
表示這個 optionalString
是一個可選類型(optional)。這裏的 String?
就是 Optional<String>
的意思。
在使用變量的時候:
若是這樣寫編譯器是會報錯的,
optionalString.lowercased()
複製代碼
這時候編譯器會提示你在 optionalString
後面添加一個?
這個問號就是告訴編譯器這個 optionalString
是一個可選類型。
class Person {
var name: String!
var son: Son!
}
class Son {
var name: String?
}
var p: Person?
print(p?.son.name)
// Playground: nil
複製代碼
這段代碼表示,若是在一行代碼裏的某個地方出現 nil 着, 這行代碼也將會返回 nil。(這一點有點相似 oc 中給 nil 發送消息)。這樣寫有一個好處,就是在維護代碼的時候, 看到 ?就知道這個東西是可選類型了。也就是告訴咱們在程序運行期間這個東西是可能爲空的。
接下來就是 !
了。這個東西跟?
同樣。
在聲明一個變量或者屬性的時候:
var something: String!
複製代碼
這個用法有一個專門的叫法:隱式解包可選類型。 這是一個特殊的可選類型,在對他的成員或者是方法進行訪問的時候,編譯器會自動的幫咱們自動解包。也就說說編譯器會自動幫咱們加上!
這個符號。換成咱們本身的話能夠這樣理解:
**在聲明一個變量或者屬性的時候,若是咱們明確的知道在程序運行過程當中訪問到這個變量或者屬性的時候,他的值必定不爲空。那麼就可使用隱式解包可選類型。**若是要舉例的話: 我想我會舉 XIB 的例子。從 SB 或者 XIB 中拖出來的控件, 都是這樣子聲明的。
在訪問變量或者屬性的時候:
對於一個可選類型來講, 有些時候編譯器會提醒咱們在這個對象後面添加!
就像最開始我朋友發給個人代碼同樣:
let pageiid = (self.pageid?.intValue)! + 1
複製代碼
若是一個方法須要傳入的是一個不可選類型做爲參數。這時候若是強制傳入一個可選類型的話。編譯器就會報錯,而且提醒咱們在這個可選類型變量後面添加一個!
。 這個作法就是強制解包。 至關因而直接訪問這個可選類型的 .some
。
這個只須要花一句話就可以講清楚了,這是給這個 optional 默認值。
var optionalString: String?
var defaultValue = optionalString ?? "defaultValue"
複製代碼
若是 optionalString
有值的話 defaultValue
就是 optionalString
的值。反之就是 "defaultValue"
。
大概講完了一些基本的概念。下面就來講說如何避免在代碼中出現各類 ?
\ !
的狀況。其實對新手來講。幾乎都是由於編譯器提示,而後自動加上去的各類 ?
和 !
。 不得不說,這樣的代碼是很是醜陋的。要解決這個問題, 知道 optional 的原理固然是最重要的。
寫了一段時間以後,我發現不少時候 optional 的使用都是沒有什麼意義的。就像我朋友給個人例子同樣。咱們能夠經過設置初始值的方法來避免使用 optional
var pageid: Int = 0
複製代碼
經過這種方法就可以避免使用到 optional, 也就不會有下面的事情了。固然還可使用懶加載:
lazy var tableView = UITableView()
複製代碼
保證在第一次使用這個屬性的時候這個屬性是確定被初始化出來了的。
固然還能夠經過使用隱式解包可選類型去避免以後的代碼中出現 ?
\ !
可是這個實際上是不被鼓勵的。
默認不要隱式解包可選類型。 在大多數場景中你均可能會忘掉這件事情。可是在一些特殊狀況下應該這樣作來減小編譯器的壓力。並且咱們也須要去理解這件事情背後的邏輯。
既然設計出來的 Optional 確定在編碼的過程當中不可避免的要使用到它。那麼在使用 Optional 的時候怎麼去避免出現像最開始的那種狀況呢?
仍是來看這行代碼:
let pageiid = (self.pageid?.intValue)! + 1
複製代碼
在這裏若是 pageid
爲空的話, 強制解包是確定會崩潰的。這種狀況應該怎麼寫呢?除了最開始說的聲明的時候設置初始值,還有就是給默認值。另外還有 Optional Map 這種方法來訪問 Optioal:
if let optionalVal = optional {
// do someThing
}
// 等價於
optional.map{ // do someThing }
複製代碼
另外還有一些我在網上搜集的 snippet 也可以很舒服的解決一些問題:給 Optional 加一個 extension:
extension Optional { }
複製代碼
添加一些方法:基本上都來自 GitHub 這個庫
能夠強制要求某個 Optional 在當前行不爲空,爲空的話會跑出異常。這個至關因而優化了強制解包的異常信息:
/// 強制要求這個 optional 不爲空
///
/// 這個方法返回 optional 的值,或者在optional 爲空的時候觸發 error
///
///
/// - Parameters:
/// - hint: 爲空拋出的錯誤信息
///
/// - Returns: optional 的值.
func require(hint hintExpression: @autoclosure() -> String? = nil,
file: StaticString = #file,
line: UInt = #line) -> Wrapped {
guard let unwrapped = self else {
var message = "required value was nil \(file), at line \(line)"
if let hint = hintExpression() {
message.append(". Debugging hit: \(hint)")
}
#if !os(Linux)
let exception = NSException(name: .invalidArgumentException,
reason: message,
userInfo: nil)
exception.raise()
#endif
preconditionFailure(message)
}
return unwrapped
}
複製代碼
/// 用來代替 ?? 操做符, 這樣寫可讀性高些
///
/// - Sample:
// var a: String? = nil
// let res = a.or("b")
func `or`(value: Wrapped?) -> Optional {
return self ?? value
}
複製代碼
/// 用來判斷這個 Optional 是否是爲空了。
var hasSome: Bool {
switch self {
case .none: return false
case .some: return true
}
}
複製代碼
// 若是 optional 不爲空的話,執行閉包, 並返回這個 Optional
@discardableResult
func ifSome(_ handler: (Wrapped) -> Void) -> Optional {
switch self {
case .some(let wrapped): handler(wrapped); return self;
case .none: return self
}
}
// 若是 optional 爲空的話,執行閉包, 並返回這個 Optional
@discardableResult
func ifNone(_ handler: () -> ()) -> Optional {
switch self {
case .some: return self;
case .none: handler(); return self
}
}
複製代碼
爲了不寫出盡是 ?
和 !
的代碼, 掌握 Swift 中可選類型的基本知識是很必要的。另外也須要去了解 Swift 中爲何要引入可選類型這個概念。在編寫 Swift 代碼的時候,須要咱們程序員時刻知道程序的邏輯是怎麼樣的,在設計一個類的時候, 要清楚它的屬性在其生命週期中那些是可能爲空的。在沒有必要的時候,儘可能的避免是用 Optional 減小 Optional 的使用,一方面能讓你的代碼邏輯更可控,一方面也能讓你的代碼更漂亮。至少不會再被編譯器一步一步的搞出那些噁心的東西。想清楚邏輯, 合理的規避, 加上一些小手段, 讓代碼更漂亮, 是一件很幸福的事情。 Optional 可以讓代碼邏輯更明確, 減小不少沒必要要的crash,若是不當使用, crash 也不會少哦。
最後我一直以爲,掌握了 Optional 是怎麼回事, 以及 Optional 怎麼用最好, 基本上就算是入門了 Swift 了。
對了朋友的代碼:
var pageiid: NSString? // 這是屬性聲明
let pageiid = (self.pageid?.intValue)! + 1 // 這是某個方法裏面的代碼
複製代碼
爲何要 NSString?這個我真不知道, 他只說了接口要一個字符串;爲何要 Optional?這個我也不知道;爲何要強制解包?這我知道,確定是 Xcode 自動幫他改的啊😂
而後我給他改爲了這樣:
var pageid: Int = 0
self.pageid +=1
"\(self.pageid)"
複製代碼
初始化的時候默認初始值爲0,避免 Optional 的使用也避免了後面的強制解包。
使用 Int 代替 NSString。第一是不想用 NSString, 第二是,在業務邏輯中,這個值應該就是 int 類型的
在接口組裝參數的地方,將 int 轉換成字符串。這個邏輯應該是接口的事情,不該該拿業務層的邏輯去將就它。