函式
函式的定義
有回傳值的函式
形參默認是let,也只能是let
func sum(v1: Int, v2: Int) -> Int {
return v1 + v2
}
無回傳值的函式
本質回傳值的就是一個空元組
// 三種寫法相同
func sayHello() -> Void {
}
func sayHello() -> () {
}
func sayHello() {
}
隱式回傳
如果整個函式體是一個單一的運算式,那么函式會隱式的回傳這個運算式
func sum(v1: Int, v2: Int) -> Int { v1 + v2 }
sum(v1: 10, v2: 20)
回傳元組,實作多回傳值
func calculate(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int) {
let sum = v1 + v2
return (sum, v1 - v2, sum >> 1)
}
let result = calculate(v1: 20, v2: 10)
print(result.sum, result.difference, result.average)
函式的檔案注釋
可以通過一定格式書寫注釋,方便閱讀
/// 求和【概述】
///
/// 將2個整數相加【更詳細的描述】
///
/// - Parameter v1: 第1個整數
/// - Parameter v2: 第2個整數
/// - Returns: 2個整數的和
///
/// - Note:傳入2個整數即可【批注】
///
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}

詳細參照Apple官方的api設計準則
引數標簽(Argument Label)
可以修改引數標簽
func gotoWork(at time: String) {
}
gotoWork(at: "8:00")
可以省略引數標簽,為了閱讀性一般不建議省略
func sum(_ value1: Int, _ value2: Int) -> Int {
value1 + value2
}
sum(5, 5)
默認引數值(Default Parameter Value)
引數可以有默認值
func check(name: String = "nobody", age: Int, job: String = "none") {
print("name=\(name), age=\(age), job=\(job)")
}
check(name: "Jack", age: 20, job: "Doctor")
check(name: "Jack", age: 20)
check(age: 20, job: "Doctor")
check(age: 20)
C++的默認引數值有個限制:必須從右往左設定;由于Swift擁有引數標簽,因此沒有此類限制
但是在省略引數標簽時,需要特別注意,避免出錯
// 這里的middle不可以省略引數標簽
func test(_ first: Int = 10, middle: Int, _ last: Int = 30) { }
test(middle: 20)
可變引數(Variadic Parameter)
一個函式最多只能有一個可變引數
func sum(_ numbers: Int...) -> Int {
numbers.count
}
sum(1, 2, 3, 4)
緊跟在可變引數后面的引數不能省略引數標簽
// 引數string不能省略標簽
func get(_ number: Int..., string: String, _ other: String) { }
get(10, 20, string: "Jack", "Rose")
我們可以參考下Swift自帶的print函式

print(1, 2, 3, 4, 5)
print(1, 2, 3, 4, 5, separator: " ", terminator: "\n")
輸入輸出引數(In-Out Parameter)
可以用inout定義一個輸入輸出引數:可以在函式內部修改外部實參的值
func swapValues(_ v1: inout Int, _ v2: inout Int) {
let tmp = v1
v1 = v2
v2 = tmp
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)
官方自帶swap的交換函式就是使用的inout

可以利用元組來進行引數交換
func swapValues(_ v1: inout Int, _ v2: inout Int) {
(v1, v2) = (v2, v1)
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)
可變引數不能標記為inout

inout引數不能有默認值

inout引數只能傳入可以被多次賦值的
常量只能在定義的時候賦值一次,所以下面會報錯

inout引數的本質是地址傳遞
我們新建個專案,通過反匯編來觀察其本質

leaq表示的就是地址傳遞,可以看出在呼叫函式之前先將兩個變數的地址放到了暫存器中

函式多載(Function Overload)
函式多載的規則
- 函式名相同
- 引數個數不同 || 引數型別不同 || 引數標簽不同
func sum(value1: Int, value2: Int) -> Int { }
// 引數個數不同
func sum(_ value1: Int, _ value2: Int, _ value3: Int) -> Int { }
// 引數標簽不同
func sum(_ a: Int, _ b: Int) -> Int { }
// 引數型別不同
func sum(_ a: Double, _ b: Double) -> Int { }
回傳值型別和函式多載無關

默認引數值和函式多載一起使用產生二義性時,編譯器并不會報錯(C++中會報錯)
// 不建議的寫法
func sum(_ value1: Int, _ value2: Int, _ value3: Int = 5) -> Int { v1 + v2 + v3 }
func sum(_ value1: Int, _ value2: Int) -> Int { v1 + v2 }
// 會呼叫第二個函式
sum(10, 2)
可變引數、省略引數標簽、函式多載一起使用產生二義性時,編譯器有可能會報錯

行內函式(Inline Function)
如果開啟了編譯器優化(Release模式默認會開啟優化),編譯器會自動將某些函式變成行內函式

我們分別來觀察下更改Debug模式下的優化選項,編譯器做了什么
1.我們新建一個專案,專案代碼如下

2.然后我們先通過反匯編觀察沒有被優化時的編譯器做了什么

可以看到會呼叫test函式,然后test函式里面再執行列印

3.現在我們開啟Debug模型下的優化選項,然后運行程式

發現print列印直接就在main函式里執行了,沒有了test函式的呼叫程序
相當于print函式直接放到了main函式中,編譯器會將函式呼叫展開成函式體

哪些函式不會被行內
- 函式體比較長
- 包含遞回呼叫
- 包含動態派發(運行時的多型呼叫)
我們可以使用@inline關鍵字,來主動控制編譯器是否做進行優化
@inline(nerver):永遠不會被行內,即使開啟了編譯器優化
@inline(nerver) func test() {}
@inline(__alaways):開啟編譯器優化后,即使代碼很長,也會被行內(遞回呼叫和動態派發除外)
@inline(__alaways) func test() {}
在Release模式下,編譯器已經開啟優化,會自動決定哪些函式需要行內,因此沒必要使用@inline
函式型別(Function Type)
每一個函式都是有型別的,函式型別由形參型別、回傳值型別組成
// () -> Void 或 () -> ()
func test() {}
// (Int, Int) -> Int
func sum(a: Int, b: Int) -> Int {}
// 定義變數
var fn: (Int, Int) -> Int = sum
fn(5, 3) // 呼叫時不需要引數標簽
函式型別作為函式引數
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {
v1 - v2
}
func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
mathFn(a, b)
}
printResult(sum, 5, 2)
printResult(difference, 5, 2)
函式型別作為函式回傳值
func next(_ input: Int) -> Int {
input + 1
}
func previous(_ input: Int) -> Int {
input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
forward ? next : previous
}
forward(true)(3)
forward(false)(3)
回傳值是函式型別的函式叫做高階函式
typealias
用來給型別起別名
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
typealias Date = (year: String, mouth: String, day: String)
func getDate(_ date: Date) {
print(date.day)
print(date.0)
}
getDate(("2011", "9", "10"))
typealias IntFn = (Int, Int) -> Int
func difference(v1: Int, v2: Int) -> Int {
v1 - v2
}
let fn: IntFn = difference
fn(20, 10)
func setFn(_ fn: IntFn) { }
setFn(difference)
func getFn() -> IntFn { difference }
按照Swift標準庫的定義,Void就是空元組()

嵌套函式
將函式定義在函式內部
func forward(_ forward: Bool) -> (Int) -> Int {
func next(_ input: Int) -> Int {
input + 1
}
func previous(_ input: Int) -> Int {
input - 1
}
forward ? next : previous
}
forward(true)(3)
forward(false)(3)
可選項(Optional)
可選項,一般也叫可選型別,它允許將值設定為nil
在型別名稱后面加個問號?來定義一個可選項
var name: String? = nil
如果可選型別定義的時候沒有給定值,默認值就是nil
var age: Int?
等價于
var age: Int? = nil
如果可選型別定義的時候賦值了,那么就是一個Optional型別的值
var name: String? = "Jack" // Optional(Jack)
可選型別也可以作為函式回傳值使用
var array = [1, 2, 3, 4]
func get(_ index: Int) -> Int? {
if index < 0 || index >= array.count {
return nil
}
return array[index]
}
強制解包(Forced Unwrapping)
可選項是對其他型別的一層包裝,可以理解為一個盒子
- 如果為
nil,那么它就是個空盒子 - 如果不為
nil,那么盒子里裝的是:被包裝型別的資料
var age: Int?
age = 10
age = nil
可選關系的型別大致如下圖

如果要從可選項中取出被包裝的資料(將盒子里裝的東西取出來),需要使用感嘆號!進行強制解包
var age: Int? = 10
var ageInt = age!
ageInt += 10 // ageInt為Int型別
如果對值為nil的可選項(空盒子)進行強制解包,將會產生運行時錯誤

可選項系結(Optional Binding)
我們可以判斷可選項是否包含值
let number = Int("123") // number為Int?
if number != nil {
print(number!)
}
還可以使用可選項系結來判斷可選項是否包含值
如果包含就自動解包,把值賦給一個臨時的常量(let)或者變數(var),并回傳true,否則回傳false
if let number = Int("123") {
print("字串轉換整數成功:\(number)")
// number是強制解包之后的Int值
// number作用域僅限于這個大括號
} else {
print("字串轉換整數失敗")
}
如果判斷條件有多個,可以合并在一起,用逗號,來分隔開
if let first = Int("4") {
if let second = Int("42") {
if first < second && second < 100 {
print("\(first) < \(second) < 100")
}
}
}
等于
if let first = Int("4"),
let second = Int("42"),
first < second && second < 100 {
print("\(first) < \(second) < 100")
}
while回圈中使用可選項系結
let strs = ["10", "20", "abc", "-20", "30"]
var index = 0
var sum = 0
while let num = Int(strs[index]),
num > 0 {
sum += num
index += 1
}
空合并運算子(Nil-Coalescing Operator)
我們可以使用空合并運算子??來對前一個值是否有值進行判斷,如果前一個值為nil,就會回傳后一個值


詳細用法如下
a ?? b
a是可選項
b是可選項或者不是可選項
b跟a的存盤型別必須相同
如果a不為nil,就回傳a
如果a為nil,就回傳b
如果b不是可選項,回傳a時會自動解包
結果的型別取決于??后面的值型別是什么
let a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int , 1
let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int , 2
多個??一起使用
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3
let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3
var a: Int??? = 10
var b: Int = 20
var c: Int? = 30
print(a ?? b) // Optional(Optional(10))
print(a ?? c) // Optional(Optional(10))
??和if let配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print(c)
}
if let c = a, let d = b {
print(c)
print(d)
}
guard陳述句
當guard陳述句的條件為false時,就會執行大括號里面的代碼
當guard陳述句的條件為true時,就會跳過guard陳述句
guard陳述句適合用來“提前退出”
guard 條件 else {
// do something....
退出當前作用域
// return
}
當使用guard陳述句進行可選項系結時,系結的常量(let)、變數(var)也能在外層作用域中使用
func login(_ info: [String : String]) {
guard let username = info["username"] else {
print("請輸入用戶名")
return
}
guard let password = info["password"] else {
print("請輸入密碼")
return
}
// if username ....
// if password ....
print("用戶名:\(username)", "密碼:\(password)", "登錄ing")
}
隱式解包(Implicitly Unwrapped Optional)
在某些情況下,可選項一旦被設定值之后,就會一直擁有值
在這種情況下,可以去掉檢查,也不必每次訪問的時候都進行解包,因為他能確定每次訪問的時候都有值
可以在型別后面加個感嘆號!定義一個隱式解包的可選項
let num1: Int! = 10
let num2: Int = num1
if num1 != nil {
print(num1 + 6)
}
if let num3 = num1 {
print(num3)
}
如果對空值的可選項進行隱式解包,也會報錯

用隱式解包的可選項型別,大多數是希望別人要給定一個不為空的值,如果別人傳的是個空值那就報錯,目的就是制定你的規則,更多適用于做一個介面來接收引數;
更多還是建議不使用該型別
字串插值
可選項在字串插值或者直接列印時,編譯器會發出警告

至少有三種方法消除警告
var age: Int? = 10
print("My age is \(age!)") // My age is 10
print("My age is \(String(describing: age))") // My age is Optional(10)
print("My age is \(age ?? 0)") // My age is 10
多重可選項
1.看下面幾個可選型別,可以用以下圖片來決議
var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
print(num2 == num3) // true

可使用lldb指令frame variable -R或者fr v -R查看區別

2.看下面幾個可選型別,可以用以下圖片來決議
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
print(num3 == num1) // false(因為型別不同)
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1

不管是多少層可選項,一旦賦值為nil,就只有最外層一個大盒子
可使用lldb指令frame variable -R或者fr v -R查看區別

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/274704.html
標籤:iOS
