Swift3.0 - 函数和闭包

Swift3.0 - 真的很简单
Swift3.0 - 数据类型
Swift3.0 - Array
Swift3.0 - 字典
Swift3.0 - 可选值
Swift3.0 - 集合
Swift3.0 - 流控制
Swift3.0 - 对象和类
Swift3.0 - 属性
Swift3.0 - 函数和闭包
Swift3.0 - 初始化和释放
Swift3.0 - 协议protocol
Swift3.0 - 类和结构体的区别
Swift3.0 - 枚举
Swift3.0 - 扩展
Swift3.0 - 下标
Swift3.0 - 泛型
Swift3.0 - 异常错误
Swift3.0 - 断言
Swift3.0 - 自动引用计数(strong,weak,unowned)
Swift3.0 - 检测API
Swift3.0 - 对象的标识
Swift3.0 - 注释
Swift3.0 - 元类型
Swift3.0 - 空间命名
Swift3.0 - 对象判等
Swift3.0 - 探究Self的用途
Swift3.0 - 类簇
Swift3.0 - 动态调用对象(实例)方法
Swift3.0 - 文本输出
Swift3.0 - 黑魔法swizzle
Swift3.0 - 镜像
Swift3.0 - 遇到的坑

函数的几种类型

  • 无参无返
func greet() -> Void {
}
// 或者
func greet(){    
}
  • 有参无返
func greet(person: String, day: String) {
    return "Hello \\\\(person), today is \\\\(day)."
}
greet(person: "Bob", day: "Tuesday")

思考1: 如何省略外部参数名?

  greet("John", "Wednesday")
 // 实现代码
  func greet(_ person: String, _ day: String) -> String {
    return "Hello \\\\(person), today is \\\\(day)."
}
  • 有参有返
func greet(_ person: String, on day: String) {
    return "Hello \\\\(person), today is \\\\(day)."
}
  • 无参有返
  func greet(_ person: String, on day: String) -> String {
    return "Hello \\\\(person), today is \\\\(day)."
}

中级思考

  • 参数和返回值

1.参数可以是那些?

基本类型的值,对象,数组,字典,元组,可变数量的参数,函数,闭包函数,协议,结构体,枚举值

2.怎么定义参数

a. 单值

 func calculate(a:Int){
    let b = a
}

b.多值

func calculate(a:Int...){
    for _ in a{
     }
}
// 调用
calculate(a: 1,2,3,4,5,6)

c.元组

func calculate(a:(name:String,age:Int)){
    let name = a.name;
    let age = a.age;
}

d.数组

 func calculate(a:[String]){
    for student in a {
}
}

e.定义字典

 func calculate(a:[String:Int]){
    for student in a {
        print(student.key)
        print(student.value)
  }
}

f.函数作为参数

 func add(a:Int,b:Int)->Int{// 作为函数参数的函数
  return a+b
}

func calculate(a:(Int,Int)->Int){// 定义的参数为函数的函数
    a(2,1)// 执行函数
}
calculate(a: add);// 执行函数

g.上面函数的闭包写法

  calculate { (a,b) -> Int in
    return a+b
}
calculate { (a,b) in a+b } // 省略写法(由于swift有推断能力,这样写它就能帮你推断出来上面的写法)

h. 参数为协议的方法

 protocol Player{  // 定义协议
      func play()
}

func playMusicWithPlayer(player:Player){
    player.play()
}

i.参数为结构体

struct Student{
    var name:String
    var age:Int
};

func getStudentDescription(student:Student){
    print(student.name)
    print(student.age)
}

j.参数为枚举类型

// 定义枚举值
enum CarType:String{
  case Lincoln = "林肯"
  case MERCURY = "水星"
  case SUZUKI = "铃木"
}
// 参数为协议的方法
func describeCar(carType:CarType){
    print(carType.rawValue);
}
  • 函数的内部定义函数

需求: 创建一个接口,输入true 返回 两个数相加的函数,输入false 返回两个数相减的函数

func generateFuncByFlag(flag:Bool)->(Int,Int)->Int{
// 定义两数字相加函数
func add(a:Int,b:Int)->Int{
    return a+b;
}
// 定义两数字相减函数
func decrease(a:Int,b:Int)->Int{
    return a-b;
}
// 根据输入的条件返回对应的函数
if flag{
    return add
}else{
    return decrease
}
}
// 生成对应的函数
let addFunc = generateFuncByFlag(flag: false)
// 执行返回的函数
print(addFunc(1,2))
  • 设置默认参数值
func addStudent(student:(name:String,score:Double)=("姓名",12)){
    print(student.name)
    print(student.1)
}
addStudent()
addStudent(student: ("酷走天涯",99))

提示:

元组类型,不能分别给参数赋值,比如像下面这样

// 这样是错误的方式
func addStudent(student:(name:String = "酷走天涯",score:Double = 12 )){
print(student.name)
print(student.1)
}
  • inout的使用

需求: 创建一个函数,交换两个Int类型值

a.如果参数为let修饰的常量

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

提示:

报错:系统提示错误,说常量不能修改值

b.我们将参数变成变量var

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

提示:

报错,不能使用var 修饰参数

c.inout 修饰的参数可以修改值

func swapTwoInts(  a: inout Int, b:inout Int){
    let temporaryA = a
    a = b
    b = temporaryA
}
var a = 30
var b = 40
swapTwoInts(a: &a, b: &b)
print(a)
print(b)

运行结果:

40
30

你需要注意的

1.inout的位置 在: 后面,数据类型前面
2.inout 修饰的参数不能有默认值
3.inout 不能用于修饰多值(如a:Int...)

  • 定义函数类型的变量
  func swapTwoInts(  a: inout Int , b:inout Int){
    let temporaryA = a
    a = b
    b = temporaryA
  }

  var swap1:( inout Int, inout Int)->Void = swapTwoInts

注意:函数类型的变量不能用标签修饰参数

// 错误的写法  不能使用a,b标签
var swap1:( a :inout Int, b: inout Int)->Void = swapTwoInts
// 你应该像下面这样
var swap1:( _ :inout Int, _: inout Int)->Void = swapTwoInts
// 或者下面这样也可以,a,b 不一定要和实际函数对应
var swap1:( _ a:inout Int, _ b: inout Int)->Void = swapTwoInts
// 建议还是用下面这种
var swap1:( inout Int, inout Int)->Void = swapTwoInts
  • 定义闭包类型数据
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let customerProvider = { customersInLine.remove(at: 0)}
print(customersInLine.count)
print("Now serving \\\\(customerProvider())!")
print(customersInLine.count)

运行结果:

5
Now serving Chris!
4
提示:上面那种闭包其实是五参有返的闭包形式,原形如下

let customerProvider:()->String= { customersInLine.remove(at: 0)}
  • 关键字 @discardableResult

先看一段代码:

class OSStudent{
var name:String!
var score:Double!
func setNewScore(score:Double)->Bool{
    if name == nil || name.isEmpty{
    return false
    }
    self.score = score
    return true
}
}
OSStudent().setNewScore(score: 34.0)

注意:

函数的setNewScore 方法有返回值,但是调用的时候,没有使用常量或者变量接受这个返回值,系统会产生警告如下图

让学习成为一种习惯

我们通过加关键字@discardableResult去除那种警告

@discardableResult
func setNewScore(score:Double)->Bool{
    if name == nil || name.isEmpty{
    return false
    }
    self.score = score
    return true
}

注意

如果你没有添加这个关键字,系统默认添加的是 @warn_unused_result ,有返回值没有使用会发生警告


高级思考

  • 如何获取,函数自己的名称,在那个文件中,在文件多少行
// 定义一个获取获取函数名称,获取文件路径的函数
func getFunctionName(name:String = #function,line:Int =   #line,file:String = #file){
print(name)
print(line)
print(file)
}
// 比如我们要获取下面函数的信息,只需要将函数写入要获取信息函数的内部调用即可
func  getUserName(){
 getFunctionName()
 }
 // 执行函数
getUserName()

运行结果:

getUserName()
152
/var/folders/gk/zc__29js08g1g03xrzgl8m1m0000gn/T/./lldb/2184/playground65.swift

  • 编译器可能没有那么智能
// 定义一个父类
class Person{
}
// 定义一个男人
class Man:Person{
}
// 定义一个女人
class Woman:Person{
}

// 定义三个描述人的方法
func describePerson(_ person:Person){
    print("我是人类")
}

func describePerson(_ woman:Woman){
    print("我是女人")
}

func describePerson(_ person:Man){
    print("我是男人")
}

 // 定义一个描述男人的女人的方法
func descripePerson(_ person:Person,_ woman:Woman){
  describePerson(person)
  describePerson(woman)
}
// 执行
descripePerson(Man(), Woman())

结果:

我是人类
我是女人

分析:

参数man 在值没有传入之前,被默认为Person 进行编译了,所以不管我们传入男人或者女人都之调用人类描述的方法。

那么我们应该怎么处理这个问题呢?

func descripePerson(_ person:Person,_ woman:Woman){
if person is Woman{
    describePerson(person as! Woman)
}else{
    describePerson(person as! Man)
}
describePerson(woman)
}

运行结果:

我是男人
我是女人

下面这种写法也是可以的

func descripePerson(_ person:Person,_ woman:Woman){
if let woman = person as?  Woman{
    describePerson(woman)
}else{
    describePerson(person as! Man)
}
describePerson(woman)
}
  • 泛型

需求: 设计一个接口,交换两个元素(数字,字符,对象)的值

  func swap<T>(a:inout T,b:inout T){
    (a,b) = (b,a)
 }

测试1

var a = "你好"
var b = "酷走天涯"
print("交换前---------------------")
print(a)
print(b)
swap(&a, &b)
print("交换后----------------------")
print(a)
print(b)

运行结果:

交换前---------------------
你好
酷走天涯
交换后----------------------
酷走天涯
你好

测试2

class Woman{
    var name = "女人"
    init(name:String) {
        self.name = name
    }
}
print("交换前---------------------")
print(a.name)
print(b.name)
swap(&a, &b)
print("交换后----------------------")
print(a.name)
print(b.name)

运行:

交换前---------------------
小红
小白
交换后----------------------
小白
小红

提示

交换的必须是相同的对象

*@escaping 用法

var downloadComplate:(Bool)->()
func downloadResource(url:String,complate:(Bool)->()){
  downloadComplate = complate
  // 异步下载,下载完成调动
  downloadComplate(true)
  // 下载失败
  downloadComplate(false)
}

运行

编译报错,提示没有加@escaping

@escaping 作用

我们经常在下载等异步操作完成时,才调用闭包函数,我们有可能暂时不要把这个闭包存放在数组中,或者使用属性去引用它,那么这个时候就需要使用这个关键了

修改代码

var downloadComplate:((Bool)->())
func downloadResource(url:String,complate:@escaping (Bool)->())  {
  downloadComplate = complate
 // 异步下载,下载完成调动
  downloadComplate(true)
  // 下载失败
  downloadComplate(false)
}

报错提示:

downloadComplate 使用之前必须初始化

所以进行初始化

var downloadComplate:((Bool)->())! // 加? 也可以,但是在调用时,要进行解包
  func downloadResource(url:String,complate:@escaping (Bool)->())  {
  downloadComplate = complate
 // 异步下载,下载完成调动
  downloadComplate(true)
  // 下载失败
  downloadComplate(false)
}

我们如何调用

downloadResource(url: "www.baidu.com") { (flag) in
print(flag)
}

如果我们不需要引用完全可以不使用关键字@escaping

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
  • 关键字@autoclosure 的用法

a.不加自动闭包的关键字@autoclosure

func serve(customer customerProvider: () -> String) {
    print(customerProvider())
}
serve { () -> String in
    return "没加@autoclosure"
}

运行结果:

没加@autoclosure

b.添加@autoclouse

func serve(customer customerProvider: @autoclosure () -> String)     {
    print (customerProvider())
}
serve(customer: "加了@autoclosure") // 调用

是不是感觉参数像是字符串,而是下面这样,系统帮你自动闭包了

serve(customer: { "加了@autoclosure"})

如果还不清楚,其实是参数是一个返回值

serve(customer: { return "加了@autoclosure"})

完整的写法其实是下面这样

serve(customer: { () in return "加了@autoclosure"})

c. @autoclosure 和 @escaping 组合使用方法

func serve(customer customerProvider: @autoclosure @escaping() -> String) {
customerProvider1 = customerProvider
print (customerProvider())
}
serve(customer:  customersInLine.remove(at: 0))

提示:

其实自动闭包给人可能造成一种表意不清的感觉,建议使用的时候,一定要注释说明,或者不要使用。

d. @noescape

func calculate(fun :@noescape ()->()){
}

提示:

1.系统默认为@onescape 的类型
2.不能被引用
3.不能在异步执行

推荐阅读更多精彩内容