最近參加朋友舉辦的開發(fā)者聚會爷肝,我分享了 Swift 的特性,可以寫出更有 Swift 味道的代碼陆错,也更加規(guī)范灯抛。
解決的問題
通常來說,編程語言的特性出現(xiàn)的原因是為了解決開發(fā)中不愉快的問題音瓷。接下來要介紹的特性提供了關(guān)于聲明和類型的更多信息对嚼。
Format
和大多數(shù)情況一樣,特性的格式有帶/不帶參數(shù)兩個版本
- 不帶參數(shù)版本
@attribute name - 帶參數(shù)版本
@attribute name(attribute arguments)
接下來開始介紹各個特性了绳慎。
autoclosure - 自動封裝成無參數(shù)的閉包
被修飾的參數(shù)會自動帶上閉包屬性
//沒有使用 autoclosure猪半,調(diào)用函數(shù)顯得別扭
func printWhenTrue(pred: () -> Bool) {
if (prod()) {
print(“I am waiting for you.")
}
}
func printWhenTrue{ 2 > 1 }
// 使用 autoclosure,調(diào)用函數(shù)自然得多
func printWhenTrue(prod: @autoclosure () -> Bool) {
if (prod()) {
print(“I am here.")
}
}
func( 2 > 1 )
學(xué)會了之后偷线,思考在什么場景適合使用磨确。先看系統(tǒng)是如何使用的,看 fatalError
的定義声邦。
// Unconditionally print a `message` and stop execution.
@noreturn public func fatalError(
@autoclosure message: () -> String = default,
file: StaticString = default,
line: UInt = default)
autoclosure
的用途是使調(diào)用變得簡單乏奥,試想一下,如果 message
參數(shù)是一個閉包{ xxx }
顯得多么的累贅亥曹〉肆耍總結(jié)出來是,autoclosure
讓調(diào)用者用的舒心媳瞪,在調(diào)用有可能變得更加簡單的場景下都考慮是否加上該特性骗炉,如斷言就十分合適。
noescape - 參數(shù)與函數(shù)的生命周期綁定
參數(shù)將不會被存儲用作后續(xù)的計算,其用來確保不會超出函數(shù)調(diào)用的生命周期蛇受。光看這段文字比較難理解句葵,通過代碼理解起來更加輕松。
//這是一個輔助理解的函數(shù),參數(shù) code 的生命周期和 doItMore 一樣
func doItMore(@noescape code: (Int) -> ()) {}
//參數(shù) code 和 doIt 的生命周期一樣
func doIt(@noescape code: () -> ()) {
code()
doItMore(code)
/* what we CANNOT do *****
// pass it as a non-`@noescape` parameter
dispatch_async(dispatch_get_main_queue(), code)
// store it
let _code:() -> () = code
// capture it in another non-`@noescape` closure
let __code = { code() }
*/
}
任何改變參數(shù) code 生命的行為都會被拒絕乍丈。code()
沒有改變生命周期剂碴,doItMore(code)
也沒有改變生命周期,因為函數(shù)doItMore(_:)
的參數(shù)帶上了 noescape
轻专,然而dispatch_async
改變了生命周期忆矛,_code
強引用參數(shù)code
,也改變了生命周期请垛,__code
也是如此催训。
因為參數(shù)的生命與函數(shù)綁定,因此不能是執(zhí)行異步操作的閉包宗收,其中一個適用的場景是瞳腌,開源庫規(guī)范使用者的使用方法,避免難以察覺的 bug镜雨。
值得一提的是,noescape
帶來了一個好處儿捧,不需要顯式的使用 self.xxx荚坞。
class Bar {
var i = 0
func some() {
doIt {
//并不需要寫成 print(self.i)
print(i)
}
}
}
noreturn - 函數(shù)或方法就不會返回到它的調(diào)用者中去
系統(tǒng) API fatalError
和 preconditionFailure
使用了 noreturn
,其定義如下菲盾。
@noreturn public func fatalError(
@autoclosure message: () -> String = default,
file: StaticString = default,
line: UInt = default)
@noreturn public func preconditionFailure(
@autoclosure message: () -> String = default,
file: StaticString = default,
line: UInt = default)
調(diào)用 fatalError
后颓影,程序就會崩潰,打印出錯誤日志懒鉴,調(diào)用 preconditionFailure
后诡挂,效果和 fatalError
一樣。
這兩個 API 均改變了程序正常的執(zhí)行流程临谱,你的程序已經(jīng)失去控制了璃俗。這效果適用于程序錯誤處理,如斷言悉默,在不滿足條件可以讓程序崩潰城豁。同樣,你也可以自定義自己的函數(shù)抄课,在出錯打印出你關(guān)心的信息唱星。
對于一個沒有用 noreturn
特性標記的函數(shù)或方法,可以將它重寫為用該特性標記的。相反,對于一個已經(jīng)用 noreturn
特性標記的函數(shù)或方法,則不可以將它重寫為沒使用該特性標記的跟磨。借助這特性间聊,可以重寫某函數(shù),當在不該調(diào)用的地方調(diào)用時抵拘,直接讓程序崩潰哎榴。
inline - 內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)用得好,才能提高效率。通常來說叹话,簡短的函數(shù)應(yīng)當使用內(nèi)聯(lián)函數(shù)偷遗,冗長的函數(shù)不適宜用內(nèi)聯(lián)函數(shù),不長不短的函數(shù)由系統(tǒng)決定驼壶,不要干預(yù)就好氏豌。Swift 也是這樣的原則,用inline
帶上參數(shù)的形式顯式指定是否內(nèi)聯(lián)热凹。
// @inline(never) 顯式指定永遠不實用內(nèi)聯(lián)函數(shù)
@inline(never) func randomInt() -> Int{
return Int(arc4random_uniform(UInt32.max))
}
// @inline(__always) 顯式指定永遠使用內(nèi)聯(lián)函數(shù)
@inline(__always) func randomInt() -> Int{
return Int(arc4random_uniform(UInt32.max))
}
//有時候是內(nèi)聯(lián)泵喘,有時候又不是,有系統(tǒng)決定
func randomInt() -> Int{
return Int(arc4random_uniform(UInt32.max))
}
inline
是一個小技巧般妙,簡短的內(nèi)聯(lián)函數(shù)不見得高效非常多纪铺,感覺這個特性的最佳使用場景是提高代碼的逼格。
NSCopying - 修飾類的存儲型變量屬性
使屬性的setter
與屬性值的一個副本合成,這個值由copyWithZone(_:)
方法返回,而不是屬性本身的值碟渺。但是鲜锚,該屬性的類型必需遵循 NSCopying
協(xié)議。在 Swift 中苫拍,有引用類型和值類型芜繁,NSCopying
只適用于引用類型,而值類型是自動復(fù)制的绒极。
熟悉 Objective-C 朋友骏令,應(yīng)該知道 @property (nonatomic, copy) NSString *myString
的涵義,給 myString
賦值時垄提,會拷貝一份榔袋,他們的內(nèi)存地址并不相同。在 Swift 中铡俐,NSCopying
就是做這樣的事情凰兑。學(xué)習(xí)最好不要眼高手低,寫一段代碼熟悉特性审丘。
// 基礎(chǔ)類聪黎,輔助理解
class Foo : NSObject, NSCopying {
var bar = "bar"
// 只有遵循 NSCopying 協(xié)議的屬性才可被 NSCopying 修飾
func copyWithZone(zone: NSZone) -> AnyObject {
let copy = Foo()
copy.bar = bar
return copy
}
}
class Test : NSObject {
// NSCopying 修飾 foo
@NSCopying var foo : Foo?
convenience override init() {
self.init(foo: nil)
}
init(foo : Foo?) {
self.foo = foo
super.init()
}
}
接下來看 NSCopying
如何發(fā)揮作用。
- 只有在初始化方法被調(diào)用之后备恤,
NSCopying
才會發(fā)揮作用稿饰,不然和普通的引用類型的屬性沒有區(qū)別,下面用兩段類似的代碼來說明露泊。
let foo = Foo()
foo.bar = "initial"
let test = Test(foo: foo)
print(foo === test.foo) // true
foo.bar = "changed"
print(test.foo!.bar) // "changed"
let foo = Foo()
foo.bar = "initial"
let test = Test()
test.foo = foo
print(foo === test.foo) // false
foo.bar = "changed"
print(test.foo!.bar) // "initial"
- 訪問屬性時不會賦值一個值喉镰,再返回給調(diào)用者,用代碼驗證惭笑。
let foo = Foo()
foo.bar = "initial"
let test = Test()
test.foo = foo
foo.bar = "changed"
//在訪問屬性時侣姆,是沒有 copy
let readFoo = test.foo!
print(readFoo === test.foo!) // true
readFoo.bar = "changed"
print(test.foo!.bar) // "changed"
使用場景是想用引用類型生真,在合適的時候又能像值類型那樣深復(fù)制賦值。不過不建議這樣捺宗,會增加應(yīng)用的復(fù)雜度柱蟀,如非要這么做,請深入思考蚜厉。
寫在最后
覺得 Swift 的特性很有意思长已,也有很多,我會在 Part 2 繼續(xù)介紹昼牛。在學(xué)習(xí)這么細節(jié)的知識時术瓮,跑操場(Playground)是很有幫助的,建議讀者們也跑跑操場贰健,理解更加深刻胞四。
在分享過程中,我關(guān)注這些特性的應(yīng)用場景是什么伶椿,不僅要懂知識點辜伟,也要知道最佳使用模式。讀者在閱讀時候如果想到更多的應(yīng)用場景脊另,請留下你的評論导狡,感謝。