Swfit的學習大致有幾個重點:
元組,可選型,函數,閉包
這些是較之OC有很大不同的地方,學會了這些swift也算是入門了。
閉包有三種形式:
全局函數 | 嵌套函數 | 閉包表達式 |
---|---|---|
有名字但不能捕獲任何值。 | 有名字,也能捕獲封閉函數內的值。 | 無名閉包,使用輕量級語法,可以根據上下文環境捕獲值。 |
捕獲值
- 閉包可以在其定義的上下文中捕獲常量或變量。 即使定義這些常量和變量的原域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。我認爲可以理解爲捕獲增加了原常量或變量的引用計數。
- Swift最簡單的閉包形式是嵌套函數,也就是定義在其他函數的函數體內的函數。 嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
不理解的可以看捕獲值
全局函數
什麼叫全局函數?全局函數不能捕獲值?啥意思呢?一陣懵逼。
let value = 1;
func testGlobalFunc()
{
//這不是捕獲了外部的value嗎?
print(value);
}
testGlobalFunc()
我的理解是這樣的:
全局函數是定義在類之外的不屬於任何類的函數。
寫OC的時候常常會用到各種宏定義,但是Swift中沒有宏,通常是通過全局常量或者全局函數來實現這一效果.我們只需要建一個文件(假設叫Global.swift),把想用的定義在裏面,無須導入頭文件什麼的,就可以在全局用啦.
在類中實現的函數是成員函數,也就是我們一般說的函數
//
// Global.swift
//
// Created by apple on 2017/6/15.
//
import Foundation
import UIKit
/**
* 替代oc中的#define,列舉一些常用宏
*/
let kScreenWidth = UIScreen.main.bounds.size.width
// 屏幕的物理寬度
func kScreenWidthFun() ->CGFloat
{
return kScreenWidth
}
func kScreenHeighFun() ->CGFloat
{
return UIScreen.main.bounds.size.height
}
現在就可以直接調用kScreenHeighFun了。這就是全局函數,不需要類或對象調用,就和全局變量kScreenWidth一樣
有人會問kScreenWidthFun不是捕獲了kScreenWidth嗎?我認爲全局變量是跟隨項目的生命週期的,所以這裏只是調用,不能算捕獲/。
閉包表達式
語法:
{
(參數)->
返回值in
函數實現
}
*這裏的參數,可以是inout(輸入輸出參數),但不能設定默認值
*元組也可以作爲參數或者返回值
*"in"關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。即由in引入函數
閉包是一種簡化的函數,函數類型由參數和返回值
組成
例如:(Int) -> Int就是一個函數類型
閉包類型和函數一樣,閉包類型也是這個樣子的。
所以:test : (Int) ->(Int);對於變量test可以賦值爲一個函數,也可以賦值爲一個閉包
//一般形式
let add:(Int,Int)->(Int) = {
(a:Int,b:Int) -> Int in
return a + b
}
print(add(10,5))
//Swift可以根據閉包上下文推斷參數和返回值的類型,所以上面的例子可以簡化如下
let add2:(Int,Int)->(Int) = {
a,b in //省略了返回箭頭和參數及返回值類型,以及參數周圍的括號。當然你也可以加括號,爲了代碼的可讀性: (a,b) in
return a + b
}
print(add2(10,5))
//Swift 自動爲內聯函數提供了參數名稱縮寫功能,您可以直接通過$0,$1,$2來順序調用閉包的參數。
//如果你在閉包表達式中使用參數名稱縮寫, 您可以在閉包參數列表中省略對其定義, 並且對應參數名稱縮寫的類型會通過函數類型進行推斷。in 關鍵字同樣也可以被省略
//一般不建議這樣寫,可讀性太差
let add3:(Int,Int) -> Int = {
return $0+$1;//知道要傳入兩個Int值,所以$0代表第一個參數,$1代表第二個參數
}
print(add3(10,5));
//單行表達式閉包可以隱式返回,如下,省略return
let add4:(Int,Int)->(Int) = {
(a,b) in
a + b
}
print(add4(10,5))
//如果閉包沒有參數,可以直接省略“in”
let add5:()->Int = {return 10 + 5}
print(add5())
//這個是既沒有參數也沒返回值,所以把return和in都省略了
let add6:()->Void = {print("15")}
add6()
//inout參數,不太常用,因爲我們直接使用外部值就行,沒必要再作爲參數傳遞一下
var addValue : Int = 10;
let test:(inout Int,Int)->(Int) = {
(a,b) in
return a + b
}
print(test(&addValue,5))
let test2:(Int,Int)->(Int) = {
(a,b) in
addValue +=(a + b);
return addValue//能夠直接修改addValue沒必要使用inout傳入
}
print(test2(10,5))
閉包類型
可以用關鍵字“typealias”聲明一個閉包數據類型。類似於OC中的typedef
typealias AddBlock = (Int, Int) -> (Int)
//typedef int (^testBlock)(int,int);//OC中block類型的定義
let Add:AddBlock = {
(c,d) in
return c + d
}
let Result = Add(10,5)
print("Result = \(Result)")
尾隨閉包
若將閉包作爲函數最後一個參數,可以省略參數標籤,然後將閉包表達式寫在函數調用括號後面。尾隨閉包讓代碼更簡潔
func testFunction(testBlock: ()->Void){
//這裏需要傳進來的閉包類型是無參數和無返回值的
testBlock()
}
//正常寫法
testFunction(testBlock: {
print("正常寫法")
})
//尾隨閉包寫法
testFunction(){
print("尾隨閉包寫法")
}
//若只有一個閉包參數,也可以把括號去掉,也是尾隨閉包寫法。推薦寫法
testFunction {
print("去掉括號的尾隨閉包寫法")
}
逃逸閉包
- 當一個閉包作爲參數傳到一個函數中,需要這個閉包在函數執行結束之後才被執行,我們就稱該閉包從函數中逃逸。一般如果閉包在函數體內涉及到異步操作,但函數卻是很快就會執行完畢並返回的,閉包必須要逃逸掉,以便異步操作的回調。
- 逃逸閉包一般用於異步函數的回調,比如網絡請求成功的回調和失敗的回調。語法:在函數的閉包行參前加關鍵字“@escaping”。
//逃逸閉包
//一下函數模仿了網絡請求,主線成刷新UI的功能,此時的閉包類似OC中的Block
var comletionHandle: ((String) -> Void) = {
str in
print("逃逸閉包執行完畢")
};
func loadData(completion:@escaping (String)->()) {
print("當前線程\(Thread.current))")
DispatchQueue.global().async {
//異步網絡請求//開啓一個異步線程
print("當前線程\(Thread.current))")
Thread.sleep(forTimeInterval: 3)
let json = "JSON"
DispatchQueue.main.async {
//回到了主線程
print("當前線程\(Thread.current)");
completion(json)//函數結束後纔去調用的閉包這個就是逃逸閉包
}
print("異步線程執行結束")
}
print("loadData函數return")
}
//1.傳入的閉包參數是定義好的閉包變量
loadData(completion: comletionHandle);
/*2.傳入的參數是尾隨閉包
loadData{
//這也是逃逸閉包
str in
print("逃逸閉包執行完畢")
};
*/
可以看到loadData先return了,此時comletionHandle還沒有執行,所以comletionHandle就叫做逃逸閉包,它逃離了loadData的生命週期。
自動閉包
語言解釋完全沒有看代碼理解的快,直接上代碼
func test(_ closure: ()-> Bool){
if closure(){
print("the result is true")
}
}
//1直接調用方法
test({ () -> Bool in
return 2 > 1
})
//2省略參數說明
test({ return 2 > 1 })
//3:使用尾部閉包方式,閉包體在圓括號之外
test(){ return 2 > 1 }
//4:在 Swift 中對閉包的用法可以進行一些簡化,在這種情況下我們可以省略掉 return,寫成:
test({ 2 > 1})
//5:還可以更近一步,因爲這個閉包是最後一個參數,所以可以使用尾隨閉包 (trailing closure) 的方式把大括號拿出來,然後省略括號,變成:
test{2 > 1}
上面的代碼是開始就講過的,尾隨閉包。但是test{2 > 1}總是看起來彆扭,因爲方法的調用不是 test()
這種嗎?自動閉包就是實現從{}
到()
的轉變
//////自動閉包autoclosure 標記自動閉包
func test(_ closure: @autoclosure ()-> Bool){
if closure(){
print("the result is true")
}
}
test(2>1)
//Swift 將會把 2 > 1 這個表達式自動轉換爲 () -> Bool。這樣我們就得到了一個寫法簡單,表意清楚的式子。
當閉包作爲函數參數時,可以將參數標記 @autoclosure
來接收自動閉包。 @autoclosure
暗含了非逃逸閉包的特性,如果你想讓這個自動閉包具有逃逸的特性需要更改標記爲 @autoclosure(escaping)
。