ch06--函数
//闭包是函数块,可以被传递,并在代码中使用,swift中的闭包和c和oc中的block类似 //全局函数和嵌套函数实际上是特别的闭包,闭包有三种形式: //1.全局函数是闭包,有名字,不能捕获任何值 //2.嵌套函数是闭包,有名字,可以从他们的封闭函数中获得值 //3.闭包表达式是没名字的闭包,用轻量级语法写成,可以从他们的包围多内容中获得值 //swift的闭包表达式有着简洁的风格,做了优化,这些优化包括: //1.从内容中推断参数和返回值类型 //2.从单一闭包表达式中模糊返回 //3.容易记的参数名字 //4.尾随闭包语法
//排序方法 //swift标准库提供了一种排序方法sort(_:),为已知类型的数字排序,一旦完成排序过程,sort(_:)方法回返回一个和原来数组类型相同,长度相同的新的数组,新的数组中的元素已经是排好顺序的,通过sort(_:)方法,原始数组不能修改 //下面闭包表达式的例子用sort(_:)方法给String类型的数组按字母倒叙排序 let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] //sort(_:)方法接受一个闭包作为其参数,这个闭包有两个和数组内容相同的类型的参数,返回一个Bool值来确定第一个参数是否应该在第二个参数前面,如果第一个参数在第二个参数前面,那么排序闭包返回true,反之返回false //这个例子是给String类型数组排序,因此排序闭包需要一个函数类型:(String, String) -> Bool func backwards(s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversed = names.sort(backwards) //print(reversed) // reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"] //如果第一个字符串s1比第二个字符串s2大(greater than), backwards(_:_:) 函数将返回true,暗示在排序的数组中,s1应该出现在s2前面,比如:"B" is “greater than” the letter "A" //闭包表达式语法: //{ (<#parameters#>) -> <#return type#> in //<#statements#> //} //闭包表达式语法可以用常量或者变量参数,或者输入输出参数,不允许默认值,如果你命名可变参数的参数,那么可变参数多参数可以被用,元组可以被用作参数和返回类型 //上面backwards(_:_:) 函数的闭包表达式如下: reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } ) //闭包的体以关键字in 开始,这个关键字暗示闭包的参数和返回类型已经定义完成, //由于闭包的体很短,甚至可以写在一行 reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2} ) //从内容中推断类型 //因为排序闭包作为参数被传递给函数,swift可以推断出参数和返回值的类型。sort(_:)方法被String类型的数组调用,因此他的参数一定是(String, String) -> Bool类型的函数,这意味着(String, String) 和 Bool没必要写成闭包表达式的一部分,所有的类型都可以被推断出来,-> 和 ()也可以被忽略 reversed = names.sort( {s1, s2 in return s1 > s2 } ) //当传递一个闭包给函数或者方法,并且和闭包表达式一致的时候,总是可以推断出参数和返回值的类型,结果你没必要写成和闭包表达式定义的那样,当闭包作为函数或者方法的参数时候 //从简单闭包表达式模糊返回-Implicit Returns from Single-Expression Closures //通过忽略return关键字,简单闭包表达式可以模糊返回闭包表达式的结果 reversed = names.sort( { s1, s2 in s1 > s2} ) //sort(_:) method的参数是函数类型,很明显闭包一定要返回Bool值,因为闭包体包含表达式(s1 > s2),返回Bool值,所以return关键字可以省略 //速记参数名字 //swift自动提供了和闭包表达式一致的速记参数名字,通过名字是 $0, $1, $2等等,它可以被用作闭包参数的值 //如果在你的闭包表达式中用速记参数名字,你可以忽略闭包的参数列表,速记参数名字的类型和数量将会从期待到函数类型中推断出来,关键字in也可以省略,因为闭包表达式由他的体组成: reversed = names.sort( { $0 > $1 } ) //$0 和 $1 指的是闭包的第一个和第二个String参数 //操作函数-Operator Functions //上面的闭包表达式甚至有根简单的写法,swift的String类型用操作符>定义了一个特别的字符串实现形式,作为函数有两个String类型的参数,返回Bool类型的值。这和sort(_:)方法需要的函数类型很搭配,所以你仅仅给一个>操作符,swift将会推断出你想用字符串的特别实现形式: reversed = names.sort(>)
//如果你想传递一个闭包表达式给函数,最为函数的最后一个参数,并且闭包表达式很长,那么你可以将它写成尾随闭包, //尾随闭包时一个闭包表达式,被写在函数括号的外面 func someFunctionThatTakesAClosure(closure: () -> Void) { // function body goes here } // here's how you call this function without using a trailing closure: someFunctionThatTakesAClosure ({ // closure's body goes here }) // here's how you call this function with a trailing closure instead: someFunctionThatTakesAClosure() { // trailing closure's body goes here } reversed = names.sort() { $0 > $1} //如果闭包表达式时函数或者方法的唯一参数,并且事尾随闭包,那么在函数或者方法名后面的括号()也可以省略, reversed = names.sort { $0 > $1} //当闭包表达式很长,并且在一行写不开对时候,尾随闭包很有用,比如:swift的Array类型有map(_:)方法,这个方法将一个闭包表达式作为他的参数,对与数组里的每一个item,这个闭包都会被调用一次,并且返回一个可替代的映射值(可能是其他类型的值)来替代这个item,映射和返回值的类型的本质是留给封闭来指定 //在每一个item应用这个被提供的闭包之后,map(_:) 会返回一个新的数组,涵盖了所有的映射值,顺序和原来数组的顺序一致 //你可以用 map(_:) 方法和尾随闭包,将Int类型的数组转换为String类型的数组,数组[16, 58, 510]被用作产生一个新的数组["OneSix", "FiveEight", "FiveOneZero"]: let digitNames = [0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"] let numbers = [16, 58, 510] //这段代码定义了一个在整型数字和他对应的英文名字之间映射的字典, //现在你可以用numbers数组产生一个String数组,通过传递一个闭包表达式给数组 的map(_:)方法,作为尾随闭包 let strings = numbers.map { (var number) -> String in var output = "" while number > 0 { output = digitNames[number % 10]! + output number /= 10 } return output } // strings is inferred to be of type [String] // its value is ["OneSix", "FiveEight", "FiveOneZero"] //对应数组里的每一个item,map(_:)方法都会调用闭包表达式一次,你没有必要指定闭包输入参数number的类型,因为类型可以从被映射的数组中的值推断出来 //在这个例子中,闭包的number参数被定义为可变参数,因此在闭包体中参数值可以被修改,闭包表达式制定了返回值类型为String类型,暗示了返回值类型将被存储在映射的数组中, //说明:digitNames字典的下标(subscript)被标记为!,因为字典下标返回一个可选值,暗示如果key不存在,字典会查找失败,在这个例子中,很肯定的是,number % 10将会是合法下标key,因此!被用作强制讲String类型值存储在下标可选中
//闭包可以捕获闭包体中的常量或者变量值 //在swift中,闭包捕获值的最简单例子是嵌套函数。嵌套函数可以捕获任何他函数参数外面的值,也可以捕获任何常量或者变量,定义在嵌套函数外面的 // func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } //makeIncrementer的返回类型是() -> Int,意味着他返回一个函数而不是一个简单的值,返回的函数没有参数,并且在每次被调用的时候返回一个Int类型的值, //makeIncrementer(forIncrement:)函数有一个Int类型的参数,这个参数有一个外部名字forIncrement,和一个本地名字amount, //makeIncrementer定义了一个嵌套函数叫做incrementer,来执行实际的增加,这个函数简单将amount和runningTotal想加,然后返回这个结果 //func incrementer() -> Int { // runningTotal += amount // return runningTotal //} //incrementer()函数没有参数,但是他从他的函数体中指定runningTotal和amount,从所在的函数中捕获runningTotal和amount,并将他们用在自己的函数体内。当调用makeIncrementer函数结束的时候,runningTotal和amount没有消失,而且runningTotal在下次incrementer被调用的时候仍然能获得 let incrementByTen = makeIncrementer(forIncrement: 10) //print(incrementByTen()) // returns a value of 10 //print(incrementByTen()) // returns a value of 20 //如果你创建另一个增加,那么他将会参照值(reference)存储给一个新的runningTotal变量 let incrementBySeven = makeIncrementer(forIncrement: 7) //print(incrementBySeven()) // returns a value of 7 //print(incrementBySeven()) // returns a value of 14 //再次调用incrementByTen继续增加他自己的runningTotal变量,不影响incrementBySeven捕获到的runningTotal变量 //print(incrementByTen()) // returns a value of 30
//如果你分配一个闭包给两个不同的常量或者变量,这两个常量或者变量仍然参照同一个闭包体 let alsoIncrementByTen = incrementByTen //print(alsoIncrementByTen()) // returns a value of 40
//闭包据说可以躲避函数,当闭包作为参数传递给函数的时候,但是闭包会在函数返回的时候调用。当你声明一个函数,函数将闭包作为他的参数之一,你可以在参数名字前面写关键字@noescape,这暗示闭包不允许躲避,用@noescape标记闭包,会让编译器做更多优化, func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) { closure() } //正如上面的例子中, sort(_:)方法有一个闭包参数,用来比较元素的大小,参数被 @noescape标记,因为在排序完成后,它保证不需要 //闭包可以躲避的一种方式是: 存储在变量中,这个变量定义在函数的外面,比如:很多函数开始异步操作,将闭包参数最为完成的处理,在开始异步操作之后,函数返回,直到异步操作被完成,闭包才会被调用,这个闭包需要躲避,稍后被调用,例子如下: var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: () -> Void){ completionHandlers.append(completionHandler) } //someFunctionWithEscapingClosure(_:)函数将闭包作为他的参数,将参数添加到数组中,这个数组在函数外面被定义,如果你尝试用@noescape标记这个参数,那么编译会报错 // class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNoescapeClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() //print(instance.x) // prints "200" completionHandlers.first?() //print(instance.x)
//自动闭包是一个闭包,自动产生另一种表达式,这种表达式作为参数传递给函数,它没有任何的参数,并且当它被调用的时候,它会返回表达式内部的值。这种语法很方便让你忽略包围函数参数的括号,通过写一个正常的表达式来代替写一个确切的闭包 // //闭包会让你延迟评估,因为直到你调用闭包的时候,闭包里的代码才会运行, var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] //print(customersInLine.count) // prints "5" let customerProvider = { customersInLine.removeAtIndex(0) } //print(customersInLine.count) // prints "5" print("Now serving \(customerProvider())!") // prints "Now serving Chris!" //print(customersInLine.count) // prints "4" //尽管customersInLine数组中的第一个元素在闭包里面被移除,但是直到闭包被实际调用的时候,数组里的元素才会被移除,如果闭包从来都没被调用,闭包内的表达式永远都不会被评估,这意味着数组里的元素永远都不会不移除,说明一点:customerProvider的类型不是String类型,而是() -> String类型的函数,这个函数没有参数,返回一个string值 //当你传递一个闭包作为参数传递给函数的时候,你也可以得到延迟评估 // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serveCustomer(customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serveCustomer( { customersInLine.removeAtIndex(0) } ) // prints "Now serving Alex!" //在上面的列表中,serveCustomer(_:)函数需要一个确切的闭包,这个闭包返回一个String类型值,下面的 serveCustomer(_:)方法执行的是相同的操作,但是代替一个确切的闭包,通过用关键字@autoclosure标记,它用的是一个自动闭包,现在你可以调用这个函数,传递一个字符串而不是一个闭包作为参数,参数会自动转换为闭包,因为customerProvider参数被标记为字@autoclosure // customersInLine is ["Ewa", "Barry", "Daniella"] func serveCustomer(@autoclosure customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serveCustomer(customersInLine.removeAtIndex(0)) // prints "Now serving Ewa!" //说明:过度使用自动闭包会使你的代码很难读懂 //@autoclosure和@noescape一样,如果你想让一个自动闭包允许逃逸,使用@autoclosure(escaping) // customersInLine is ["Barry", "Daniella"] var customerProviders: [() -> String] = [] func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) { customerProviders.append(customerProvider) } collectCustomerProviders(customersInLine.removeAtIndex(0)) collectCustomerProviders(customersInLine.removeAtIndex(0)) print("Collected \(customerProviders.count) closures.") // prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // prints "Now serving Barry!" // prints "Now serving Daniella!" //在上面的代码中,代替调用闭包作为他的参数,collectCustomerProviders(_:)函数将闭包添加到数组中,数组在函数到外面被声明,这意味着数组中的闭包在函数返回后才被执行,结果,customer参数的值允许逃逸出函数的作用范围