函数 (Functions)
- 函数的定义方法和ObjC差别很大, 第一次看起来会比较奇怪, 话说ObjC中[]这种方法调用方式都看过来了, 还有别的能难倒我们吗? 直接以例子来看吧:
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
// 接受一个String的参数, 返回一个String
//如果不需要参数不返回数据则是:
func sayHello() -> (){ // 当然也可以省略后面的 ->()
print("Hello!")
}
- 函数返回多个值
当然是利用强大的元组了,
func minMax(array: [Int]) -> (min: Int, max: Int) {
// find min value and max value in array
return (min, max)
}
// 调用
let bounds = minMax([8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
- 返回Optionals value
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
// find min value and max value in array
return (min, max)
}
// 调用
if let bounds = minMax([8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
- 函数参数名:
前面函数可以看到, 如果只有一个参数, 名字是会被省略的, 具体原因我们可以从苹果自己的命名看出来, Array里面有个joinWithSeparator(separator: String)方法, 可以看出, 苹果希望第一个参数名是包含在函数名里面的, 如果不这样做的话, 调用的时候大概是这样的:
joinWith(separator:"")
比较下:
joinWithSeparator("")
我个人是倾向于官方的版本, 因为这样一来函数重名的概率就更小了, 在找方法的时候也更迅速, 同时用模糊匹配的插件也会更快. 但是可能又有人觉得这样感觉不一致, 看大家的喜好吧
上面都是单参数的例子, 举个多参数的例子:
func someFunction(firstParameterName: Int, secondParameterName: Int) {
}
someFunction(1, secondParameterName: 2)
// 第一个参数自动隐藏, 如果一定要显示出来就多写一个标签
func otherFunction(firstParameterName firstParameterName: Int, secondParameterName: Int) {
}
otherFunction(firstParameterName: 1, secondParameterName: 2)
// 同样的 要是不想标签和变量名一样 也可以自己指定
func sayHello(to person: String, and anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(to: "Bill", and: "Ted"))
// 如果你想像C语言那么简洁, 不想要参数标签, 那么可以用下划线来隐藏掉
func addTwoNumber(a: Int, _ b: Int) {
}
addTwoNumber(1, 2)
5 默认参数值:
不是什么新鲜玩意, 但是却是有用的东西, 在C++里面早已经有了, 但是ObjC里面因为没有所以在暴露一些接口的时候非常蛋疼.
func someFunction(parameterWithDefault: Int = 12) {
print(parameterWithDefault)
}
someFunction(6) // 6
someFunction() // 12
6 不限参数
这个东西看起来比较高大上, 实际上原理应该是比较简单的, 先看例子吧:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5) // 返回3.0
arithmeticMean(3, 8.25, 18.75) //返回10.0
// 因为类型是一样的, 所以原理就是编译器直接把参数替换成为[Double]类型, 然后在调用处自己组装一个数组出来即可
7 常量参数和变量参数
函数入参默认是常量, 不允许修改的, 但是有时候不想重新定义一个变量出来, 就想要直接用这个变量就好了, 那么就加上一个在参数前面加上一个var即可, 例如:
func modifyParamFunction(var a: Int, b: Int) ->Int{
a = b
b = a // error
return a
}
8 修改参数本身
在C和ObjC语言中, 修改参数本身的值是需要传递地址的, 在Swift里面也一样, 不过因为没有指针运算符, 所以需要显式地在参数那里加一个inout, 例如:
func swapTwoInts(inout a: Int, inout _ b: Int) {
let t = a
a = b
b = t
}
var a = 1, b = 2
swapTwoInts(&a, &b) // &去掉会报错
9 函数类型
在Swift中, 函数本身也是一个变量, 自然就会有类型,例如,
上面sayHello这个函数, 类型就是 ()->(), 我们可以把它赋值给一个变量:
var sayHi: (String)->(String) = sayHello
sayHi("Ryan") // 调用之
10 函数作为参数传入
ObjC里面也有差不多的用法, 既然函数是一个对象, 且有类型, 那么直接把这个对象传入即可, 例如:
func addTwoInts(a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
return a * b
}
func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
11 函数作为返回值
与上面一样的道理, 直接看例子:
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
while currentValue != 0 {
currentValue = moveNearerToZero(currentValue)
}
12 嵌套函数
也就是函数里面还可以声明并实现函数, 只是这样一来就无法在外部访问了, 可以在一些函数里面写一些简单的辅助功能, 同时这些功能又没有需要暴露的情况, 这样可以很好地保持其封装性, 如上面的例子可以写成:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
函数大概就这么多, 只要语法看习惯了, 其实也没什么太多的区别, 在类中实例方法也是一样的定义, 类方法在func前面加个class罢了...
同样, 需要看更多细节的, 查看官方文档
闭包(Closure)
到了Swift让我惊艳的部分了, 闭包和block的作用基本是相似的, 只是比起block来, 苹果把闭包做的更加简洁, 闭包的内容很多, 但是如果有block的基础的话, 应该不会难懂.
- 闭包表达式(Closure Expressions)
我们之前提到过一次闭包, 在数组排序的时候, 在数组的sort()函数中, 我们可以传入函数, 也可以传入闭包(所以从某种角度来说, 这两个东西就是一样的)
func backwards(s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
// reversed 等于 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果用闭包来写就是这样的:
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
// 凶残的苹果对闭包的表达式进行了一轮又一轮的简化, 直达之前提过的最简表达式
// 第一轮:
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } ) // 写成一行业不算什么
// 第二轮:
reversed = names.sort( { s1, s2 in return s1 > s2 } )// 根据类型推导可以省略类型
// 第三轮:
reversed = names.sort( { s1, s2 in s1 > s2 } ) // 排序需要一个Bool值, 那么s1>s2这个表达式符合需求 省略return
// 第四轮:
reversed = names.sort( { $0 > $1 } ) // 知道会有2个参数, 入参的写法免了
// 第五轮:
reversed = names.sort(>) // 完全交给编译器来推导...
总之, 上面这些写法对于这种简单一点的闭包来说, 还是很好用的, 个人建议到一般到第三轮, 很简单到第四轮就差不多了
- 尾随闭包(Trailing Closures)
如果一个函数需要一个闭包作为参数, 那么可以把闭包写在函数调用的括号里, 也可以写在外面, 例如:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
someFunctionThatTakesAClosure({})
someFunctionThatTakesAClosure(){}
// 又比如:
reversed = names.sort() { $0 > $1 }
// 还可以连()都省略
reversed = names.sort { $0 > $1 }
当然, 这么写肯定是要求闭包是最后一个参数的
- 值捕获(Capturing Values)
简单说来就是闭包可以使用外部的一些变量或者常量, 即使外部已经不存在了, 这里依然会保留, 直接看例子吧:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回 10
incrementByTen()
// 返回 20
incrementByTen()
// 返回 30
// 由此可见, makeIncrementer函数内部的runningTotal没有被销毁, 如果没猜错的话, incrementByTen销毁了才会销毁掉(如果没有别的引用了)
// 如果又创建了一个incrementor, 则会是新的一份runningTotal
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回 7
很显然, 这里需要注意闭包中的循环引用问题
闭包是引用类型
差不多记住这句话就好了, 所以要更加注意循环引用的问题非逃逸闭包(Nonescaping Closures)
官网解释了很多, 但是听起来还是云里雾里的, 最后总算搞明白了...简单说来就是这个闭包被作为参数传进去之后, 如果函数返回了, 我们还能不能再用了, 默认是可以再用, 如果你不想让它被重复利用, 就加上@noescape, 通过例子来说吧:
var completionHandlers: [() -> Void] = [] // 存储闭包用
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
closure()
completionHandlers.append(closure) // error
}
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure { () -> Void in
print("OK")
}
completionHandlers.first?() // 把问号换成!也可以, 因为确定非空
加了@noescape的闭包, 必须当场使用, 不允许出了函数再用, 但是没有加的就可以.
- 自动闭包(Autoclosures)
还是回到Array的sort那里, 为什么可以省略掉{}呢? 因为有自动闭包, 所谓自动闭包就是把传入的参数自动包装成一个闭包, 直接看官方例子:
func serveCustomer(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// 如果函数没有@autoclosure则调用就是:
//serveCustomer({customersInLine.removeAtIndex(0)})
闭包差不多介绍到这里, 很多新的概念, 其实苹果的宗旨就是, 能怎么简洁就这么简洁, 等习惯之后看起来还是很舒服, 很自然的.
按惯例, 具体细节看看官方文档
后记
目前我也只看到这里, 所以一次更新到了闭包, 之后更新速度可能会慢, 下周还要出差...囧rz..感谢大家花时间看, 希望能有帮助, 也希望大家给出意见, 不对之处请指正.