Swift3入门教程之二----函数和闭包

文档更新说明:

•   2016年10月12日 v1.0 初稿
•   2016年10月13日 v2.0 增加闭包部分

1、函数

1.1、函数的定义和调用

  • 使用func关键字来定义一个函数
  • 在swift中,大概我们也可以自豪的说"一切皆对象"了,函数也可以当做一个特殊的对象来看待。
// 定义一个不带参数的函数
func sendMessage() {
   let message = "Hey there!"
   print(message)
}

// 调用不带参数的函数
sendMessage()

/**
输出结果:
Hey there!
*/

1.2、带参数的函数

  • 定义带参数的函数,只需要在定义的时候,括号内填入相应的参数,默认情况下,是用let声明的常量
  • 在下面的定义中,func代表定义一个函数,是个关键字,sendMessage是这个函数的名字,shouting是参数的名字,可在函数内部使用,在调用的时候需要声明写出来Bool代表参数类型,这里是一个布尔类型
// 定义一个带参数的函数
func sendMessage(shouting: Bool) {
   var message = "Hey there!"
   if shouting {
      message = message.uppercased()
   }
   print(message)
}

// 调用一个带参数的函数
sendMessage(shouting: true)


/**
输出结果:
HEY THERE!
*/

1.3、函数的参数标签

  • swift中,为了使函数在调用和定义的时候都能见名知意,提供了两个命名(参数标签和参数名),中间以空格分开
  • 参数名是给调用的时候函数内部使用的,参数标签是调用函数的时候,当做函数名来用的
  • 实际使用中,合理运用这两个参数,可以大大提高代码的可读性
  • 一个函数中,多个参数名不能重名,但是参数标签可以重名(建议不要那么无聊去写重名的)
  • 下面函数中“to”就是函数标签,recipientshouting是参数名
  • 不想在调用的时候写出参数标签,可以在定义函数的时候使用“_”来代替参数标签,(1.4)
func sendMessage(to recipient: String, shouting: Bool) {
   var message = "Hey there, \(recipient)!"
   if shouting {
      message = message.uppercased()
   }
   print(message)
}

sendMessage(to: "Morgan", shouting: false)


/**
输出结果:
Hey there, Morgan!
*/

1.4、省略参数标签

  • 有些参数名可能会跟函数名重名,比如下面这个函数,两个message,在调用的时候就会显得特别奇怪,这时,可以用"_"来代替参数名字,调用的时候看起来就更加舒服了
// 名字优化之前
func sendMessage(message: String, to recipient: String, shouting: Bool)
// 省略参数名字之后
func sendMessage(_ message: String, to recipient: String, shouting: Bool) {
   var message = "\(message), \(recipient)!"
   if shouting {
      message = message.uppercased()
   }
   print(message)
}

// 调用的时候可以省略参数名字
sendMessage("See you at the Bash", to: "Morgan", shouting: false)

/**
输出结果:
See you at the Bash, Morgan!
*/

1.5、参数的默认值

  • 在上面的函数中,其实最后那个参数我们一般都是传false,这时,调用的时候再每次都写这个参数就显得多余了
  • swift中提供了一个默认参数,即可以给函数的参数一个默认值,调用的时候不传就使用默认值
  • 建议把带默认值的参数写在后面,否则,你会回来点赞的(调用的时候参数顺序看得清晰些)
  • 默认参数在定义函数的时候写在类型后面,如下:
// 参数带有默认值的函数
func sendMessage(_ message: String, to recipient: String, shouting: Bool = false) {
   var message = "\(message), \(recipient)!"
   if shouting {
      message = message.uppercased()
   }
   print(message)
}

// 调用带有默认值的函数
sendMessage("See you at the Bash", to: "Morgan")

/**
输出结果:
See you at the Bash, Morgan!
*/

1.6、可变参数

  • 可变参数指的是:这个参数可以传入0个,也可以传入10000个,就像OC里面的NSLog()函数一样,后面参数个数的不定的
  • 可变参数的定义:在定义参数类型的后面加上省略号...,比如func someFunction(argumentLabel parameterName: Int...)parameterName参数就是个可变参数了
  • 可变参数的获取:在函数内部,可变参数以数组的形式体现的,上面定义的那个可变参数的获取方式parameterName[0],这样,就拿到了可变参数的第一个值
// 可变参数
func optParameter(optionPara: Int...) {
    for num in optionPara {
        print(num)
    }
}

// 调用可变参数函数
optParameter(optionPara: 1, 2, 3, 4, 5)


/**
输出结果:
1
2
3
4
5
*/

1.7、输入输出参数(C语言中的指针参数)

  • 在C语言中,我们可以给函数传入一个指针值,以达到在函数内部修改外部参数值的目的,swift中,这种参数叫做输入输出参数
  • 定义一个输入输出参数,只需要在参数类型前面加一个inout
  • 调用的时候,需要在输入输出参数前面加"&"
  • 调用的时候,输入输出参数必须传入变量,不能是常量
  • 输入输出参数不能有默认值
// 定义一个交换两数的方法
func swap(a:inout Int, b:inout Int) {
    let c = a
    a = b
    b = c
}

var a = 1
var b = 2

print("before: a=\(a), b=\(b)")

// 调用交换方法
swap(&a, &b)

print("after: a=\(a), b=\(b)")

/**
输出结果:
before: a=1, b=2
after: a=2, b=1
*/

1.8、函数类型

  • 回到开头,我说过,在swift中,我们也可以称为“一切皆对象”,那么,函数既然可以称之为对象,肯定有类型
  • 函数的类型:有个技巧,不管是什么类型,把参数名变量名全部删掉,剩下的就是类型了。上面的那个函数,类型就应该是(inout Int, inout Int) -> Void,解读为:这个函数类型接受两个输入输出的Int类型参数,并返回Void
  • 函数类型的使用:同其他类型一样的,上面那个函数类型就可以这样使用var function: (inout Int, inout Int) -> Void = swap
  • 同样,函数类型也可以作为函数的参数和返回值

函数作为参数的例子

// 这个函数用来作为参数
func sum(num1: Int, num2: Int) -> Int {
    return num1+num2
}

// 这个函数接受一个(Int, Int)->Int类型的函数作为参数
func printResault(num1: Int, num2: Int, ruler:(Int, Int)->Int) {
     // 调用传进来的函数
    print(ruler(num1, num2))
}

// 调用函数,并把参数传进去
printResault(num1: 4, num2: 5, ruler: sum)

/**
输出结果:
9
*/

函数作为返回值

// 减1
func stepForward(_ input: Int) -> Int {
    return input + 1
}
// 加1
func stepBackward(_ input: Int) -> Int {
    return input - 1
}

// 根据传入的backward值,返回加1函数或者减1函数
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 现在指向 stepBackward() 函数。

//调用返回的函数
print(moveNearerToZero(currentValue))

/**
输出结果:
2
*/

1.9、嵌套函数

  • 嵌套函数就是在函数里面在定义一个函数,相对的,前面说的那些都是全局函数
  • 嵌套函数只能在内部使用

2、闭包

2.1、什么是闭包

  • 闭包 就是“闭合包裹常量和变量的代码块”的简称,写过OC的人对OC中的 Block 应该不会陌生,其实闭包就是swift里面的“Block”
  • 闭包是引用类型
  • 闭包的定义和函数很像,把函数的各种名称部分去掉,在用花括号括起来,其实就是一个闭包了,闭包的一般定义形式如下:
{(params) -> returnType in 
    statemems
}
  • in关键字表示闭包的参数和返回值类型定义完成,闭包函数体开始
  • 闭包参数可以是inout类型,但是,不能有默认参数
  • 闭包完整格式实例
let grade = [90, 100, 80, 76, 88, 65, 65, 63]

print("before \(grade)")

let grade_sort = grade.sorted (by: { (num1: Int, num2: Int) -> Bool in
    return num1 > num2
})

print("after \(grade_sort)")

/**
输出结果:
before [90, 100, 80, 76, 88, 65, 65, 63]
after [100, 90, 88, 80, 76, 65, 65, 63]
*/

2.2闭包格式大简化

  • 函数参数为闭包的时候,编译器可以自动推断出闭包的类型的,比如说在下面的代码中,闭包的类型就可以省略,直接在括号内写参数名
  • 闭包作为函数最后一个参数的时候,可以省略函数调用的小括号,直接把闭包跟在函数名后面,同时,参数标签也可以省略掉,这个闭包又称做尾随闭包,经过这两项优化之后,上面的代码变成了下面这样
let grade = [90, 100, 80, 76, 88, 65, 65, 63]

print("before \(grade)")

let grade_sort = grade.sorted { (num1, num2) in
    return num1 > num2
}

print("after \(grade_sort)")

/**
输出结果:
before [90, 100, 80, 76, 88, 65, 65, 63]
after [100, 90, 88, 80, 76, 65, 65, 63]
*/

  • 如果闭包中,只有一句代码,那么闭包会自动把这句代码的结果当做返回值处理,也就是说,上面的闭包我们可以省略return关键字,省略之后,代码如下
let grade = [90, 100, 80, 76, 88, 65, 65, 63]

print("before \(grade)")

let grade_sort = grade.sorted { (num1, num2) in
   num1 > num2
}

print("after \(grade_sort)")

/**
输出结果:
before [90, 100, 80, 76, 88, 65, 65, 63]
after [100, 90, 88, 80, 76, 65, 65, 63]
*/

  • 在闭包中,参数可以使用缩写,用$表示,比如说$0就是第一个参数,这时,前面的参数定义部分已经没有意义了,in关键字也可以省略,代码如下
let grade = [90, 100, 80, 76, 88, 65, 65, 63]

print("before \(grade)")

let grade_sort = grade.sorted { $0 > $1 }

print("after \(grade_sort)")


/**
输出结果:
before [90, 100, 80, 76, 88, 65, 65, 63]
after [100, 90, 88, 80, 76, 65, 65, 63]
*/

2.3、逃逸闭包

  • 逃逸闭包指的是我们在函数内部接收了闭包之后,把闭包保存了起来,留着函数结束之后再调用这个闭包,这个闭包就叫做逃逸闭包
  • 逃逸闭包需要在定义的类型前加@escaping,否则会编译报错
// 定义一个变量,用来存储一个闭包,给后面使用
var completeHandle:() -> Void = {}

// 定义一个函数,这个函数接受一个闭包,并且设置给外部变量,这里必须标记为 @escaping
func doSomeThing(completion: @escaping () -> Void) {
    completeHandle = completion
}

print("设置之前")

// 调用函数,并设置一个闭包
doSomeThing {
    print("完成了")
}

print("设置之后")

// 调用刚才设置好的闭包
completeHandle()

print("调用之后")


/**
输出结果:
设置之前
设置之后
完成了
调用之后
*/

2.4、闭包中的self处理

  • 有过OC经验的童鞋应该都非常明白Block中不能强引用self,否则会由于循环引用而导致内存泄露
  • Swift中提供了更加简单的方法,在闭包中弱引用一个变量,只需要在闭包参数定义之前加入[weak self],Swift就会自动将self在闭包中弱引用
  • 此时需要注意的是,弱引用可能不存在了,所以在这种情况下,self变成了一个可选值
{[weak self](params) -> returnType in 
    // self变成了可选值,需要用?来使用
    self?.somePropety = ...
    
    statemems
}

相关文章

1、Swift3入门教程之一基础部分

参考资料:

  1. Swift Standard Library Playground
  2. Swift.org
  3. The Swift Programming Language (Swift 3)
  4. Using Swift with Cocoa and Objective-C (Swift 3)
  5. WWDC2016:Session 404 Getting Started with Swift