Swift学习笔记(三)--函数与闭包

函数 (Functions)

  1. 函数的定义方法和ObjC差别很大, 第一次看起来会比较奇怪, 话说ObjC中[]这种方法调用方式都看过来了, 还有别的能难倒我们吗? 直接以例子来看吧:
func sayHello(personName: String) -> String {
  let greeting = "Hello, " + personName + "!"
  return greeting
}
// 接受一个String的参数, 返回一个String
//如果不需要参数不返回数据则是:
func sayHello() -> (){ // 当然也可以省略后面的 ->() 
  print("Hello!")
}

  1. 函数返回多个值
    当然是利用强大的元组了,
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)")
  1. 返回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)")
}
  1. 函数参数名:
    前面函数可以看到, 如果只有一个参数, 名字是会被省略的, 具体原因我们可以从苹果自己的命名看出来, 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的基础的话, 应该不会难懂.

  1. 闭包表达式(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(>) // 完全交给编译器来推导...

总之, 上面这些写法对于这种简单一点的闭包来说, 还是很好用的, 个人建议到一般到第三轮, 很简单到第四轮就差不多了

  1. 尾随闭包(Trailing Closures)
    如果一个函数需要一个闭包作为参数, 那么可以把闭包写在函数调用的括号里, 也可以写在外面, 例如:
func someFunctionThatTakesAClosure(closure: () -> Void) {
  // function body goes here
}
someFunctionThatTakesAClosure({})
someFunctionThatTakesAClosure(){}
// 又比如:
reversed = names.sort() { $0 > $1 }
// 还可以连()都省略
reversed = names.sort { $0 > $1 }

当然, 这么写肯定是要求闭包是最后一个参数的

  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

很显然, 这里需要注意闭包中的循环引用问题

  1. 闭包是引用类型
    差不多记住这句话就好了, 所以要更加注意循环引用的问题

  2. 非逃逸闭包(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的闭包, 必须当场使用, 不允许出了函数再用, 但是没有加的就可以.

  1. 自动闭包(Autoclosures)
    还是回到Array的sort那里, 为什么可以省略掉{}呢? 因为有自动闭包, 所谓自动闭包就是把传入的参数自动包装成一个闭包, 直接看官方例子:
func serveCustomer(@autoclosure customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// 如果函数没有@autoclosure则调用就是:
//serveCustomer({customersInLine.removeAtIndex(0)})

闭包差不多介绍到这里, 很多新的概念, 其实苹果的宗旨就是, 能怎么简洁就这么简洁, 等习惯之后看起来还是很舒服, 很自然的.

按惯例, 具体细节看看官方文档

后记

目前我也只看到这里, 所以一次更新到了闭包, 之后更新速度可能会慢, 下周还要出差...囧rz..感谢大家花时间看, 希望能有帮助, 也希望大家给出意见, 不对之处请指正.

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

推荐阅读更多精彩内容