1.2、OC->Swift:函数与闭包

零、概述

  • 函数的一等公民,可以当做类型、函数参数、返回值等,支持面向函数编程。
  • 每个函数 都是 一个由 函数的 参数值类型 和 返回值类型 组成的类型。

一、函数基础

1、声明与定义

  • 关键字:func,参数列表:括号()包裹,返回值:->,函数体:大括号{}包裹。
func sayHelloWorld() -> String {
    print("hello, world")
}

sayHelloWorld()
// 打印“hello, world”

2、函数参数

2.1、参数列表:(参数名1:类型, 参数名2:类型, 参数名3:类型)

  • 注意:调用函数时候,入参实参 必须与 声明函数时参数列表里的参数顺序一致。

2.2、参数标签(即参数别名,和OC类似)

a、默认支持参数同名标签
func testFunc(name: String) {
}
testFunc(name: "haha")
b、支持自定义函数的参数标签
  • 如果自定义了参数的标签,调用的时候必须写具体的标签名:
func testFunc_0(name: String) {
}
testFunc_0(name: "haha")

func testFunc_1(nickname name: String) {
}
testFunc_1(nickname: "123")
c、忽略参数标签:使用一个下划线_来代替一个参数标签;调用的时候忽略标签名。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) { 
    ... 
}
someFunction(10)

2.3、支持函数默认值:(参数名1:类型, 参数名2:类型 = 默认值)

  • 当默认值被定义后,调用这个函数时可以忽略这个参数,不用入参。
  • 需要将不带有默认值的参数放在函数参数列表的最前,默认值参数位于最后面。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
    ...
}

someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) 
// 调用函数时,可以不传默认参数
someFunction(parameterWithoutDefault: 4)

2.4、可变参数:(参数名:类型 ...)

  • 一个函数最多只能拥有一个可变参数,只能位于参数列表最后面。
  • 可变参数的传入值:本质上,在函数体中变为此类型的一个数组。
func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    // numbers为double的数组
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是这 3 个数的平均数。

复合参数:默认值 + 可变参数

func testFunc(_ name:String, age:Int=0, list:[String]) {
    print("name:\(name), age:\(age), list:\(list)")
}

testFunc("hui", list: ["a", "b", "c"])
testFunc("hui", age: 18, list: ["a", "b", "c"])

2.5、输入输出参数(类似于,C语言的指针语法)

  • 注意:函数参数默认是常量,因此试图在函数体中修改参数值将会导致编译错误。
    如果希望一个函数内可以修改参数的值,并且这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数

  • 语法:参数名: inout Type,关键字inout;调用函数时,在参数名前加 & 符,表示这个值可以被函数修改。

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
// 3,107
print("\(someInt),\(anotherInt)")

swapTwoInts(&someInt, &anotherInt)
// 107,3
print("\(someInt),\(anotherInt)")

3、返回值

  • 无返回值(本质是返回Void)
func sayHello() -> Void {
    print("Hello World!");
}

// 等价于
func sayHello() {
    print("Hello World!");
}
  • 多重返回值:即返回 元组可选元组
// 返回数组中的最大值和最小值
func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// 打印“min is -6 and max is 109”

返回可选元组:如果函数返回的元组类型有可能整个元组都“没有值”。

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    ...
    return (currentMin, currentMax)
}
  • 隐式返回(即省略return关键字)
    如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。
func greeting(for person: String) -> String {
    // 没有return,默认返回字符串
    "Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// 打印 "Hello, Dave!"

二、函数类型

在Swift中,函数的一等公民,可以当做类型、函数参数、返回值等,支持面向函数编程。

1、定义与语法

  • 定义:由 函数参数类型函数返回类型 共同组成 函数类型。
  • 语法格式:(参数列表) -> 返回类型
    例如:() -> Void ,表示的是 没有参数无返回值 类型的函数
//  函数类型:(Int, Int) -> Int
func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
// 1、函数类型,做为变量
var mathFunction: (Int, Int) -> Int = addTwoInts
print("Result: \(mathFunction(2, 3))") // Prints "Result: 5"


// 2、函数类型,做为函数参数
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5) // 打印“Result: 8”

// 3、函数类型,作为返回类型
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 现在指向 stepBackward() 函数。

2、函数嵌套

  • 把函数定义在别的函数体中,称作 嵌套函数(nested functions)。
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}

var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

三、闭包

Swift的闭包类似于OC的(block) 或者 其他语言的匿名函数(lambdas)语法,其目的都是提供简易便捷的代码块封装方式。

  • 闭包的三种表现形式
    全局函数是一个有名字但不会捕获任何值得闭包。
    嵌套函数是一个有名字并且可以捕获其封闭函数域内值得闭包。
    闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值得没有名字的闭包。

  • Swift闭包看起来内容很多,其实很多是提供语法糖功能,学习的时候注意区分和思考。

  • 闭包的学习重点在于 值捕获 和 循环引用。
    闭包核心:闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 这其中涉及到的内存管理。

1、闭包的语法格式

下面将以Swift 标准库提供了名为 sorted(by:) 的方法做为闭包的讲解。

a、标准语法:

大括号{}包裹:标识整个闭包代码块;
函数类型声明:括号()内定义参数列表;箭头符号-> 闭包返回值类型;
关键字in:标识 闭包 代码语句的开始

{ (parametersList) -> returnType in
    // statements
}

例子:以集合类型的系统排序函数sorted为例

names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 等价于
names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

b、闭包 支持的 参数和返回值

  • 支持inout参数,支持可变参数,不支持默认值参数
  • 支持多参数返回值(即元组和可选元组)。

c、闭包语法糖:闭包的各种省略模式

  • 省略参数列表 和 返回值:names.sorted(by: { s1, s2 in return s1 > s2 } )
    如果上下文可以推导出 参数和返回值的类型,那么参数和返回值类型都可以省略;并且箭头-> 和 围绕在参数周围的括号()也可以被省略。(依赖于上下文类型推断原理)
  • 单表达式省略return语句: names.sorted(by: { s1, s2 in s1 > s2 } )
    这是依赖于单表达式的隐式返回原理

  • 省略参数名称:names.sorted(by: { $0 > $1 } )
    Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 ... 来顺序调用闭包的参数,以此类推。

  • 运算符方法:names.sorted(by: >)
    Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合。
    因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现。


2、闭包的值捕获 和 循环引用

2.1、捕获

  • 闭包 可以 在其被定义的上下文中 捕获 常量或变量。
    即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}
return incrementer
}

// 由于 闭包或者嵌套函数 addByTen 和 addBySix 各自分别捕获一份变量 runningTotal,
// 因此,addByTen 和 addBySix 调用不会相互干扰
let addByTen = makeIncrementer(forIncrement: 10)
let addBySix = makeIncrementer(forIncrement: 6)

print(addByTen()) // 10
print(addByTen()) // 20

print(addBySix()) // 6
print(addBySix()) // 12

print(addByTen()) // 30
  • 注意:为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。

  • 注意:如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。

2.2、闭包的循环强引用

  • 循环应用的产生(同OC循环引用一样)
    将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时,实例属性访问或实例方法的调用,这两种情况都导致了闭包“捕获”self(闭包的捕获特性),从而产生了循环强引用。

  • 循环强引用的产生,是因为闭包和类相似,都是引用类型。

2.3、循环强引用的解决:捕获列表

  • 语法:[unowned/weak 变量/常量名称, unowned/weak 变量/常量名称, ...]
    捕获列表 中的 每一项 都是由 一对元素组成;元素是由 weakunowned 关键字,紧跟着 类实例的引用(例如 self)或初始化过的变量(如 delegate = self.delegate!);这些项在方括号中用逗号分开。

  • 捕获列表 放在 闭包的函数类型 之前

// 1、如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // 这里是闭包的函数体
}
  • 省略 闭包的函数类型
// 2、如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 in 放在闭包最开始的地方:
lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // 这里是闭包的函数体
}

2.4、弱引用weak 和 无主引用unowned的区别

  • 弱引用(和OC中的weak类似)
    在被捕获的引用可能会变为 nil 时,将闭包内的捕获定义为 弱引用。
    弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 nil。

    注意:如果被捕获的引用绝对不会变为 nil,应该用无主引用,而不是弱引用。

  • 无主引用
    在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 无主引用。


3、逃逸闭包,关键字@escaping

  • 注意:swift默认的闭包都是非逃逸闭包。

3.1、逃逸闭包 的 定义:

  • 定义和使用场景:
    当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。

/* 
* 函数接受一个 闭包completionHandler 作为参数,该闭包被添加到一个函数外定义的数组中。
* 如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
*/
var completionHandlersList: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlersList.append(completionHandler)
}

3.2、类方法 与 逃逸闭包

如果将一个闭包标记为 @escaping 意味着,必须在闭包中显式地引用 self

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        /* 1、逃逸闭包 */
        someFunctionWithEscapingClosure { 
            self.x = 100  // 逃逸闭包,必须显示的引用 self
        }
      
        /* 2、非逃逸闭包 */
        someFunctionWithNonescapingClosure { 
            x = 200 // 非逃逸闭包,可以隐式的引用 self
        } 
    }
}

3.3、为什么要分逃逸闭包和非逃逸闭包?

  • 非逃逸闭包不会产生循环引用,它会在函数作用域内释放,编译器可以保证在函数结束时闭包会释放它捕获的所有对象,因此可以在非可逃逸闭包里放心的使用self关键字;
  • 非逃逸闭包的另一个好处是编译器可以应用更多强有力的性能优化,就可以省去一些保留(retain)和释放(release)的调用;
  • 非逃逸闭包它的上下文的内存可以保存在栈上而不是堆上。

4、语法糖:尾随闭包

函数调用时,针对最后一个函数参数是比闭包情况 的省略模式。

  • 适用场景:函数的最后一个参数是闭包的情况下。
    如果你需要将一个很长的 闭包表达式作为最后一个参数 传递给函数,尾随闭包 可以简化这个过程。
func someFunctionThatTakesAClosure(closure: () -> Void) {
    ...
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    ...
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    ...
}
  • 省略括号
    如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉。
names.sorted() { $0 > $1 }
// 等价于
names.sorted { $0 > $1 }

5、语法糖:自动闭包,关键字@autoclosure

  • 定义:
    自动闭包 是一种自动创建的闭包,用于将 参数类型是函数类型的参数 自动包装为一个闭包,只要在参数名之前标识@autoclosure即可。这种闭包是不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。

    这种便利语法让你能够省略闭包的花括号{},用一个普通的表达式来代替显式的闭包。

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
// 参数 customerProvider 的类型:函数类型() -> String,
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出“Now serving Alex!”



// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}

// 自动闭包:省略闭包的花括号`{}`,用一个普通的表达式来代替显式的闭包。
serve(customer: customersInLine.remove(at: 0))
// 打印“Now serving Ewa!”
  • 自动闭包 是可以“逃逸”:支持同时使用 @autoclosure 和 @escaping 属性。
  • 注意:过度使用 autoclosures 会让你的代码变得难以理解。

四、函数和闭包都是引用类型

无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 函数 当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作...
    Carson_Zhu阅读 361评论 0 0
  • 5-1 如何定义和使用函数 定义和调用函数 当你定义了一个函数的时候,你可以选择定义一个或者多个命名的分类的值作为...
    75b9020bd6db阅读 165评论 0 0
  • 定义 闭包是可以在代码中被传递和引用的功能性独立代码块,跟C和Objective-C- 中的代码块(blocks)...
    LK_EX阅读 639评论 0 1
  • Swift003-访问修饰词 函数 闭包 访问限制词 在 Swift 语言中,访问修饰符有五种,分别为 filep...
    DDY阅读 364评论 0 0
  • 在 Swift 中,函数和闭包都是引用类型 常量形式参数 func greet(from person: Str...
    张天宇_bba7阅读 591评论 0 2