前不久swift 2.2
發布,感覺差不多快趨于穩定了,就在工作之余把swift
拿出來重新看了一下,總結一些常用的知識點。話說好記性不如爛筆頭,放在這忘記的時候還可以拿出來瞅瞅,免得遺忘的時候到處查找,這里與大家分享一下。
1、if let 和 guard
if let
和 guard
只是語法糖,不使用也沒關系,但是使用了以后使代碼更簡潔,邏輯更加清晰,舉個例子,
我們平時寫個參數是可選型的(Optional
)函數的時候,往往需要在函數中作進一步的判斷,比如我簡單的判斷輸入的一個數是不是正數(> 0
),正常的寫法應該是這樣的
func judgeTheNumber(number: Int?) {
if number != nil {
if number > 0 {
print("This is a positive number")
}
} else {
print("none")
}
}
這里我們judgeTheNumber()
這個函數的參數是Int?
,所以我們得判斷是不是nil
,然后再判斷是不是> 0
,比較正產的寫法是這樣的這樣,但是代碼好像比較多,有沒有更簡潔的呢?
- 1、
if let
上面的代碼我們使用if let
的寫法如下,
func judgeTheNumber(number: Int?) {
if let number = number where number > 0 {
print("This is a positive number")
} else {
print("nil or zero or negative number")
}
}
這樣的效果和上面的是一樣的,通過if let number = number where number > 0
這么一句是不是,代碼更簡潔,可讀性更高了!
- 2、
guard
guard
是swift 2.0
的新特性,在Xcode 7.0
推出來的,與if
語句不同的是,guard
只有在條件不滿足時才會執行,你可以把guard
近似的看成Assert
,上面的代碼用guard
就如下,
func judgeTheNumber(number: Int?) {
guard let number = number where number > 0 else {return}
// 當number滿足上述條件時執行下面代碼,否則執行上面的return
print("This is a positive number")
}
至于if let
和 guard
語法中出現的 where
,只是附加一些條件。相當于邏輯運算 &&
和 ||
。當然上面的例子還有很多的寫法,如果不熟悉,可以多寫幾次試試。
2、@available 和 #available
- 1、
#available
在以前開發的時候,不同版本的API
兼容著實讓人頭疼,所以會看到各種#define
然后就是在代碼中各種,if (iOS 8)
等等之類的,如果有的地方不注意,比如你調用一個方法是iOS 8
才有的,然后你的版本最低支持iOS 7
,毫無疑問在iOS 7
上調用的時候會崩潰。而Swift 2.0
新引入的#available
機制,就解決了這一問題。如果你在低版本上使用高版本的方法時,編譯器檢查的時候發現你沒處理的話就會直接報錯,如下
`Deployment Target` 為 `iOS 7.0`時使用`UIAlertController`
上面提示UIAlertController
只有在iOS 8
以后才可以使用,這時點擊左側的報錯紅點就會有提示,如下
這時我們就可以看到有個提示是
Add if #available version check
就是用#available
來做版本檢查,同時原代碼上就會加上一個if else
的判斷點擊Fix-it
后代碼就變成
if #available(iOS 8.0, *) {
let alert = UIAlertController.init(title: "溫馨提示", message: "這是iOS8以后才有的方法", preferredStyle: UIAlertControllerStyle.Alert)
} else {
// Fallback on earlier versions
}
然后再編譯就不會報錯了。
#available
這里也算是對開發方式的一個改進,也更加體現出swift
一直強調的安全。這里#available(iOS 8.0, *)
中,意思就是iOS 8
以上,其中*
表示全平臺,無特殊說明的話都是*
,同時后面還可以加參數,比如#available(iOS 8.0, OSX 10.10, *)
,表示就是iOS 8
以及OSX 10.10
以上。
- 2、@available
@available
放在函數(func
),類(class
)或者協議(protocol
)前面。表明這些類型適用的平臺和操作系統。比如
@available(iOS 9.0, *)
func newMethord() {
// This func can perform after iOS 9.0
}
這樣我們就可以像下面這樣調用這個方法
guard #available(iOS 9.0, *) else {return}
newMethord()
如果你不加上#available(iOS 9.0, *)
那么當你支持的版本低于iOS 9.0
的時候毫無疑問會報錯的。
參考鏈接
1、 Availability checking in Swift 2: backwards compatibility the smart way
2、Difference between @available and #available in swift 2.0
3、#selector
首先我們來看個例子,我想彈出一個alert
,于是我寫了個方法,如下
func showMyAlertMethord() {
guard #available(iOS 8.0, *) else {return}
let alert = UIAlertController.init(title: "溫馨提示", message: "這是iOS8以后才有的方法", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .Destructive, handler: { (action: UIAlertAction) in
print("Handle OK action ")
}))
self.presentViewController(alert, animated: true, completion: {
print("UIAlertController present")
})
}
然后我在viewDidLoad()
調用showMyAlertMethord()
這個方法,運行發現彈出來了,沒有問題?,F在我想延遲三秒執行,這里我簡單的用performSelector()
這個方法,如下
self.performSelector(showMyAlertMethord(), withObject: nil, afterDelay: 3)
這時候報錯
一臉懵逼,噢,搞了半天發現原來是方法名寫錯了,這時候果斷換上
Selector("showAlert")
,如下
self.performSelector(Selector("showMyAlertMethord"), withObject: nil, afterDelay: 3)
雖然有個警告,但是不管了,運行,出來了,完美解決!
于是就用這種方法寫,于是有一天再用這個方法的時候,崩潰了!我的天,不是吧,之前都是好好的,一模一樣的代碼,查看源碼
self.performSelector(Selector("showmyAlertethord"), withObject: nil, afterDelay: 3)
哦,找了半天,原來之前是Selector("showMyAlertMethord"
,現在手一哆嗦,寫成了Selector("showmyAlertethord")
,坑爹啊,寫錯一個字母。雖然解決了問題,不過這時候你就應該有所思考,這種寫法是不對的,或者說是不安全的,再回頭過來看看那個警告
意思是讓我們用
#selector()
來代替Selector()
,所以到最后正確的代碼應該如下
self.performSelector(#selector(self.showMyAlertMethord), withObject: nil, afterDelay: 3)
這就是在swift 2.2
中提倡使用的方法#selector()
,你會發現如果你輸錯方法名編譯器會直接報錯,而不是運行期來檢查是不是有這個方法,這樣編譯期(compile-time
)來檢查出來,而不是運行期(run-time
),這樣就更安全了!
所以,在調用方法的時候,還是使用#selector()
吧,最好還是不要使用Selector()
或者NSSelectorFromString()
,因為這樣會更安全!
參考鏈接
1、@selector() in Swift?
2、Swift #selector referencing to an Objective-C method
4、inout
升級了Xcode 7.3
以后,簡單的寫一個例子,如下
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var tempStr = "My "
let result = stringPlus(tempStr)
print("result is \(result)")
print(tempStr)
}
func stringPlus(var str: String) -> String {
str = str + "Loveway !"
return str
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
輸出結果
**result is My Loveway !**
**My **
這是簡單的字符串拼接的一個例子,調用、運行并沒有什么問題,但是你會發現有一個警告,如下
沒錯,就是
'var' parameters are deprecated and will be removed in Swift 3
提示var
類型的參數已經廢棄了,并且將會在swift 3
中移除!這是因為蘋果認為var
類型的參數會有限制,var
變量參數只是在函數體內有用,超出作用域就是失效了,于是就使用了inout
來代替var
,修改代碼如下
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var tempStr = "My "
let result = stringPlus(&tempStr)
print("result is \(result)")
print(tempStr)
}
func stringPlus(inout str: String) -> String {
str = str + "Loveway !"
return str
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
輸出結果
**result is My Loveway !**
**My Loveway !**
嗯,好像沒有警告了,完美解決!等等,什么,兩次輸出的結果不一樣???第一次tempStr
的值是My
,改成inout
后的值變成My Loveway !
了,這是因為inout
是可以在函數作用域內改變參數的,并且還可以返回(不管你的函數有沒有返回值,只要是在函數作用域內改變了這個inout
參數)。
inout
語意解釋為輸入輸出參數(In-Out Parameters
),輸入輸出參數被傳遞時遵循如下規則:
- 1、函數調用時,參數的值被拷貝。
- 2、函數體內部,拷貝后的值被修改。
- 3、函數返回后,拷貝后的值被賦值給原參數
這種行為被稱為拷入拷出 (copy-in copy-out
) 或值結果調用 (call by value result
)。例如,當一個計算型屬性或者一個具有屬性觀察器的屬性被用作函數的輸入輸出參數時,其 getter
會在函數調用時被調用,而其 setter
會在函數返回時被調用。
作為一種優化手段,當參數值存儲在內存中的物理地址時,在函數體內部和外部均會使用同一內存位置。這種優化行為被稱為引用調用 (call by reference
),它滿足了拷入拷出模型的所有需求,而消除了復制帶來的開銷。不要依賴于拷入拷出與引用調用之間的行為差異。但是你不能將同一個值傳遞給多個輸入輸出參數,因為多個輸入輸出參數引發的拷貝與覆蓋行為的順序是不確定的,因此原始值的最終值也將無法確定。
現在我們就不難理解為什么 tempStr
的值發生了變化,這也是swift
要廢棄var
參數的原因。
似乎現在警告也解決了,inout
也知道了,于是繼續向下寫,寫一個數字轉英文的例子,如下
let digitNames = [0: "one", 1: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"]
let numbers: Array = [14, 15, 16]
let strArray = numbers.map( {
(var number: Int) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
})
print(strArray)
輸出結果為
**["twofour", "twofive", "twosix"]**
沒有問題,但是警告又出來了
通過上面,于是我們改成了
inout
,結果報錯
似乎在這里
inout
并不能解決問題,這種情況下,有一種消除的警告的方法就是用一個臨時變量接受這個輸入的參數,如下
let digitNames = [0: "one", 1: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"]
let numbers: Array = [14, 15, 16]
let strArray = numbers.map( {
(number: Int) -> String in
var number = number
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
})
print(strArray)
這樣就解決了問題,這個也是蘋果官方提供的一種解決思路。
參考鏈接
1、Removingvar
from Function Parameters
2、'var'
parameters are deprecated and will be removed in Swift 3
3、Swift: Declarations
5、@noescape
關于閉包(Closures
)這里就不多說了,不清楚的童鞋可以去 這里 了解一下。
我們都知道閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數比較相似。那么非逃逸閉包(@noescape
)到底是個什么意思呢,舉個栗子
如下代碼
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
funcWithEscapeClosure(clouserTest)
print("222")
}
func funcWithEscapeClosure(someFunc: () -> Void) {
someFunc()
print("111")
}
func clouserTest() -> Void {
print("333")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
那么我們會看到控制臺的輸出信息如下
**333**
**111**
**222**
從上到下看,按照函數的執行順序,這個我們都能理解,是沒錯的。這個時候我們知道clouserTest ()
是在funcWithEscapeClosure()
被調用后執行的,那么這個就是非逃逸閉包,對于非逃逸閉包,我們可以在參數名之前加一個@noescape
,用來標注這個函數是不能逃逸出函數體的,這樣做的好處就是能讓編譯器明確的知道這個函數的生命周期,以做進一步的優化。比如上面的函數我們可以改成這樣
func funcWithEscapeClosure(@noescape someFunc: () -> Void) {
clouserTest()
print("111")
}
編譯運行肯定是沒有問題的。
有逃逸閉包肯定就有非逃逸閉包,非逃逸閉包的定義是:當一個閉包作為參數傳到一個函數中,但是這個閉包在函數返回之后才被執行,我們稱該閉包從函數中逃逸。這種情況大都是在異步操作的時候用到,還是上面按個例子,我們修改一下,如下
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
funcWithEscapeClosure(clouserTest)
print("222")
}
func funcWithEscapeClosure(someFunc: () -> Void) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
someFunc()
}
print("111")
}
func clouserTest() -> Void {
print("333")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
然后看看輸出結果是
**111**
**222**
**333**
結果表明someFunc()
也就是clouserTest()
是在最后執行的,這里的clouserTest()
就從funcWithEscapeClosure()
這個函數中逃逸出了,這個時候如果在參數前面加一個@noescape
會報錯,如下
如上,提示閉包使用的非逃逸(
@noescape
)參數可能需要允許它逃逸,這是因為funcWithEscapeClosure()
這個函數執行完畢之后才會執行someFunc()
,也就是說這種情況就是someFunc()
就是逃逸出了函數,這里加@noescape
肯定是不對的。還有一點就是編譯器知曉非逃逸閉包的上下文環境,所以非逃逸閉包中可以不寫 self。比如你的類有一個
name
的變量,在非逃逸閉包中你就可以直接用name = "loveway"
,而無需self.name = "loveway"
這樣。
6、mutating
mutating
從字面意思來看就是變化、改變,我們知道在Objective-C
中只有類(class
)中才可以定義方法,然而在Swift
中,我們可以在類(class
)、結構體(struct
)、枚舉(enum
)中定義方法,這也是Objective-C
和Swift
的一個區別。下面以一個結構體舉例
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var man = Man(height: 170.0, weight: 62.0)
print(man)
man.gainHeightAndWeight(1.0, gainWeight: 2.0)
print(man)
}
struct Man {
var height = 0.0, weight = 0.0
mutating func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
}
}
}
然后輸出
**Man(height: 170.0, weight: 62.0)**
**Now height is(171.0), weight is (64.0)**
**Man(height: 170.0, weight: 62.0)**
我們可以看到控制臺輸出的結果是沒有問題的,man
的值沒有變化。不過這個時候會有一個警告,如圖
意思是說這個
man
是不可變的,建議使用let
來替代var
,不去管它。現在來修改一下代碼,如下
struct Man {
var height = 0.0, weight = 0.0
func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
// print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
height += gainHeight
weight += gainWeight
}
}
編譯就會報如下錯誤
這是因為結構體和枚舉是值類型。默認情況下,值類型的屬性不能在它的實例方法中被修改。
但是,如果你確實需要在某個特定的方法中修改結構體或者枚舉的屬性,你可以為這個方法選擇可變(
mutating
)行為,然后就可以從其方法內部改變它的屬性;并且這個方法做的任何改變都會在方法執行結束時寫回到原始結構中。方法還可以給它隱含的self
屬性賦予一個全新的實例,這個新實例在方法結束時會替換現存實例。要使用可變方法,將關鍵字mutating
放到方法的func
關鍵字之前就可以了,如下:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var man = Man(height: 170.0, weight: 62.0)
print(man)
man.gainHeightAndWeight(1.0, gainWeight: 2.0)
print(man)
}
struct Man {
var height = 0.0, weight = 0.0
mutating func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
// print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
height += gainHeight
weight += gainWeight
}
}
}
控制臺輸出
**Man(height: 170.0, weight: 62.0)**
**Man(height: 171.0, weight: 64.0)**
這個時候man
的值就改變了