
已經(jīng)有好幾個人跟我抱怨過為什么 swift 里面有那么多問號(?)還有嘆號(!)了。恰恰哈, 在剛剛開始寫 swift 的時候, 我也面臨著這種問題。
昨天一個朋友發(fā)了我一行代碼, 讓我看看應(yīng)該怎么寫:
let pageiid = (self.pageid?.intValue)! + 1
這段代碼看起來很操蛋, 但是糟心的是, 在剛剛寫 swift 的時候, 我寫過更惡心的代碼。
既然大家在剛剛開始寫 swift 的時候都遇到了這個問題。今天就來看看, 這樣的代碼應(yīng)該怎樣寫才能讓我們更爽。
我們都知道, 有問號(?)和嘆號(!)的原因是什么?—Optional
相信準(zhǔn)備要試試 swift 的人,或多或少都看到過, 或者聽說過這是 swift 相較于 oc 很大的區(qū)別。在我看來,除了語法上的變化以外, swift 和 oc 最大的區(qū)別就在 于optional 了。為了照顧到實(shí)在是太新的新手(畢竟我自己也是新手)。還是簡單的講講這個東西吧!
什么是 Optional
這一點(diǎn)就沒什么可說的,在 swift 中 Optional 實(shí)際上是一個枚舉。如果要自己實(shí)現(xiàn)一個類似的東西的話, 核心的代碼應(yīng)該是這樣的:
// 這段代碼不重要
public enum SYOptional<Wrapped> {
case none
case some(Wrapped)
}
這段代碼只是要告訴你, 這個枚舉只有兩個 case, 一個是 .none 代表這個 optional 是沒有值的, 也就是說他是 nil。另外一個值 .some 代表這個 optional 是有值的。
蘋果為什么要引入 optional 這個概念, 在這個地方就不打算贅述了??聪旅嬉欢卧?
“Optional 可以說是 Swift 的一大特色,它完全解決了 “有” 和 “無” 這兩個困擾了 Objective-C 許久的哲學(xué)概念,也使得代碼安全性得到了很大的增加?!?/p>
摘錄來自: 王巍 (onevcat). “Swifter - Swift 必備 Tips (第四版)”。 iBooks.
個人認(rèn)為 optional 確實(shí)是 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
這段代碼表示,如果在一行代碼里的某個地方出現(xiàn) nil 著, 這行代碼也將會返回 nil。(這一點(diǎn)有點(diǎn)類似 oc 中給 nil 發(fā)送消息)。這樣寫有一個好處,就是在維護(hù)代碼的時候, 看到 ?就知道這個東西是可選類型了。也就是告訴我們在程序運(yùn)行期間這個東西是可能為空的。
!
接下來就是 ! 了。這個東西跟? 一樣。
在聲明一個變量或者屬性的時候:
var something: String!
這個用法有一個專門的叫法:隱式解包可選類型。 這是一個特殊的可選類型,在對他的成員或者是方法進(jìn)行訪問的時候,編譯器會自動的幫我們自動解包。也就說說編譯器會自動幫我們加上! 這個符號。換成我們自己的話可以這樣理解:
在聲明一個變量或者屬性的時候,如果我們明確的知道在程序運(yùn)行過程中訪問到這個變量或者屬性的時候,他的值一定不為空。那么就可以使用隱式解包可選類型。如果要舉例的話: 我想我會舉 XIB 的例子。從 SB 或者 XIB 中拖出來的控件, 都是這樣子聲明的。
在訪問變量或者屬性的時候:
對于一個可選類型來說, 有些時候編譯器會提醒我們在這個對象后面添加! 就像最開始我朋友發(fā)給我的代碼一樣:
let pageiid = (self.pageid?.intValue)! + 1
如果一個方法需要傳入的是一個不可選類型作為參數(shù)。這時候如果強(qiáng)制傳入一個可選類型的話。編譯器就會報錯,并且提醒我們在這個可選類型變量后面添加一個! 。 這個做法就是強(qiáng)制解包。 相當(dāng)于是直接訪問這個可選類型的 .some 。
??
這個只需要花一句話就能夠講清楚了,這是給這個 optional 默認(rèn)值。
var optionalString: String?
var defaultValue = optionalString ?? "defaultValue"
如果 optionalString 有值的話 defaultValue 就是 optionalString 的值。反之就是 "defaultValue"。
怎么寫好 Optional
大概講完了一些基本的概念。下面就來說說如何避免在代碼中出現(xiàn)各種 ? \ ! 的情況。其實(shí)對新手來說。幾乎都是因?yàn)榫幾g器提示,然后自動加上去的各種 ? 和 ! 。 不得不說,這樣的代碼是非常丑陋的。要解決這個問題, 知道 optional 的原理當(dāng)然是最重要的。
避免使用 Optional
寫了一段時間之后,我發(fā)現(xiàn)很多時候 optional 的使用都是沒有什么意義的。就像我朋友給我的例子一樣。我們可以通過設(shè)置初始值的方法來避免使用 optional
var pageid: Int = 0
通過這種方法就能夠避免使用到 optional, 也就不會有下面的事情了。當(dāng)然還可以使用懶加載:
lazy var tableView = UITableView()
保證在第一次使用這個屬性的時候這個屬性是肯定被初始化出來了的。
當(dāng)然還可以通過使用隱式解包可選類型去避免之后的代碼中出現(xiàn) ? \ !
但是這個其實(shí)是不被鼓勵的。
默認(rèn)不要隱式解包可選類型。 在大多數(shù)場景中你都可能會忘掉這件事情。但是在一些特殊情況下應(yīng)該這樣做來減少編譯器的壓力。而且我們也需要去理解這件事情背后的邏輯。
如何訪問 Optional
既然設(shè)計出來的 Optional 肯定在編碼的過程中不可避免的要使用到它。那么在使用 Optional 的時候怎么去避免出現(xiàn)像最開始的那種情況呢?
還是來看這行代碼:
let pageiid = (self.pageid?.intValue)! + 1
在這里如果 pageid 為空的話, 強(qiáng)制解包是肯定會崩潰的。這種情況應(yīng)該怎么寫呢?除了最開始說的聲明的時候設(shè)置初始值,還有就是給默認(rèn)值。另外還有 Optional Map 這種方法來訪問 Optioal:
if let optionalVal = optional {
// do someThing
}
// 等價于
optional.map{ // do someThing }
另外還有一些我在網(wǎng)上搜集的 snippet 也能夠很舒服的解決一些問題:給 Optional 加一個 extension:
extension Optional { }
添加一些方法:基本上都來自 GitHub 這個庫
require
可以強(qiáng)制要求某個 Optional 在當(dāng)前行不為空,為空的話會跑出異常。這個相當(dāng)于是優(yōu)化了強(qiáng)制解包的異常信息:
/// 強(qiáng)制要求這個 optional 不為空
///
/// 這個方法返回 optional 的值,或者在optional 為空的時候觸發(fā) 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
}
or
/// 用來代替 ?? 操作符, 這樣寫可讀性高些
///
/// - Sample:
// var a: String? = nil
// let res = a.or("b")
func `or`(value: Wrapped?) -> Optional {
return self ?? value
}
hasSome
/// 用來判斷這個 Optional 是不是為空了。
var hasSome: Bool {
switch self {
case .none: return false
case .some: return true
}
}
ifSome ifNone
// 如果 optional 不為空的話,執(zhí)行閉包, 并返回這個 Optional
@discardableResult
func ifSome(_ handler: (Wrapped) -> Void) -> Optional {
switch self {
case .some(let wrapped): handler(wrapped); return self;
case .none: return self
}
}
// 如果 optional 為空的話,執(zhí)行閉包, 并返回這個 Optional
@discardableResult
func ifNone(_ handler: () -> ()) -> Optional {
switch self {
case .some: return self;
case .none: handler(); return self
}
}
總結(jié)
為了避免寫出滿是 ? 和 ! 的代碼, 掌握 Swift 中可選類型的基本知識是很必要的。另外也需要去了解 Swift 中為什么要引入可選類型這個概念。在編寫 Swift 代碼的時候,需要我們程序員時刻知道程序的邏輯是怎么樣的,在設(shè)計一個類的時候, 要清楚它的屬性在其生命周期中那些是可能為空的。在沒有必要的時候,盡量的避免是用 Optional 減少 Optional 的使用,一方面能讓你的代碼邏輯更可控,一方面也能讓你的代碼更漂亮。至少不會再被編譯器一步一步的搞出那些惡心的東西。想清楚邏輯, 合理的規(guī)避, 加上一些小手段, 讓代碼更漂亮, 是一件很幸福的事情。 Optional 能夠讓代碼邏輯更明確, 減少很多不必要的crash,如果不當(dāng)使用, crash 也不會少哦。
最后我一直覺得,掌握了 Optional 是怎么回事, 以及 Optional 怎么用最好, 基本上就算是入門了 Swift 了。
對了朋友的代碼:
var pageiid: NSString? // 這是屬性聲明
let pageiid = (self.pageid?.intValue)! + 1 // 這是某個方法里面的代碼
- 為什么要用 NSString?
- 為什么要 Optional
- 為什么要強(qiáng)制解包?
為什么要 NSString?這個我真不知道, 他只說了接口要一個字符串;為什么要 Optional?這個我也不知道;為什么要強(qiáng)制解包?這我知道,肯定是 Xcode 自動幫他改的啊??
然后我給他改成了這樣:
var pageid: Int = 0
self.pageid +=1
"\(self.pageid)"
初始化的時候默認(rèn)初始值為0,避免 Optional 的使用也避免了后面的強(qiáng)制解包。
使用 Int 代替 NSString。第一是不想用 NSString, 第二是,在業(yè)務(wù)邏輯中,這個值應(yīng)該就是 int 類型的
在接口組裝參數(shù)的地方,將 int 轉(zhuǎn)換成字符串。這個邏輯應(yīng)該是接口的事情,不應(yīng)該拿業(yè)務(wù)層的邏輯去將就它。