初学Swift _闭包
2016-07-14 16:41
375 查看
//: Playground - noun: a place where people can play
import UIKit
var str = "Hello, playground"
/**
*
闭包
闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift
中的闭包与 C 和 Objective-C
中的代码块(blocks)以及其他一些编程语言中的 lambdas
函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。
这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift
会为您管理在捕获过程中涉及到的所有内存操作
在函数
章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
全局函数是一个有名字但不会捕获任何值的闭包
嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
Swift
的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
利用上下文推断参数和返回值类型
隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
参数名称缩写
尾随(Trailing)闭包语法
*/
/**
*
闭包表达式
闭包表达式是一种利用简洁语法构建内联闭包的方式。
闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
下面闭包表达式的例子通过使用几次迭代展示了sorted函数定义和语法优化的方式。
每一次迭代都用更简洁的方式描述了相同的功能。
*/
/**
* sorted函数
*/
let names = ["chris", "alex", "ewa", "daniella","barry"]
func backwards(s1:String, s2:String) -> Bool {
return s1 < s2
}
var resersed = names.sort(backwards)
print(resersed)
names.sort { (s3:String, s4:String) -> Bool in
return s3 < s4
}
//单表达式闭包隐式返回
names.sort { (s6, s7) in s6 > s7 }
//参数名缩写
resersed = names.sort({$0 > $1})
print(resersed)
/**
*
运算符函数
*/
resersed = names.sort(>)
print(resersed)
/**
*
尾随闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
*/
func someFunctionThatTakesAClosure(closure:() -> ()) {
//函数数体部分
}
someFunctionThatTakesAClosure {
//闭包主体部分
}
//注意:
如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉
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]
/*
注意
字典digitNames下标后跟着一个叹号(!),因为字典下标返回一个可选值(optional
value),表明该键不存在时会查找失败。
在上例中,由于可以确定number % 10总是digitNames字典的有效下标,因此叹号可以用于强制解包
(force-unwrap)
存储在下标的可选类型的返回值中的String类型的值。
*/
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
while number > 0{
output = digitNames[number%10]! + output
number /= 10
}
return output
}
print(strings)
/**
*
捕获值
闭包可以在其被定义的上下文中捕获常量或变量。
即使定义这些常量和变量的原作用域已经不存在,
闭包仍然可以在闭包函数体内引用和修改这些值。
*/
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0;
func incrementor()-> Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 4)
/**
该例子定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会将runningTotal变量增加10的incrementor函数。调用这个函数多次可以得到以下结果:
*/
incrementByTen()
incrementByTen()
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
/*
再次调用原来的incrementByTen会在原来的变量runningTotal上继续增加值,该变量和incrementBySeven中捕获的变量没有任何联系:
*/
incrementByTen()
/**
注意
如果您将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,您将创建一个在闭包和该实例间的循环强引用。Swift
使用捕获列表来打破这种循环强引用。更多信息,请参考闭包引起的循环强引用。
*/
/**
*
闭包是引用类型
上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。
无论您将函数或闭包赋值给一个常量还是变量,您实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。
这也意味着如果您将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
*/
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
/*
非逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape能使编译器知道这个闭包的生命周期(译者注:闭包只能在函数体中被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文),从而可以进行一些比较激进的优化。
*/
func someFunctionWithNoescape(@noescape closure: () -> Void) {
closure()
}
//例子:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure {
self.x = 100;
}
someFunctionWithNoescape {
x = 200;
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first?()
print(instance.x)
/**
*
自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
这种便利语法让你能够用一个普通的表达式来代替显式的闭包,从而省略闭包的花括号。
*/
var customersILine = ["chris","alex","ewa","barry","one"]
print(customersILine.count)//print 5
let customeProvider = {customersILine.removeAtIndex(0)}
print(customersILine.count)//print 5
print("now \(customersILine.count)")//print 5
customeProvider()
print("now now \(customersILine.count)")
/*
说明:
尽管在闭包的代码中,customersInLine的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。
如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。
请注意,customerProvider的类型不是String,而是()
-> String,一个没有参数且返回值为String的函数。
*/
//将闭包作为参数传递给函数时,
你能获得同样的延时求值行为
func serveCustomer(customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer( { customersILine.removeAtIndex(0) } )
/*
说明:
serveCustomer(_:)接受一个返回顾客名字的显式的闭包。
下面这个版本的serveCustomer(_:)完成了相同的操作,不过它并没有接受一个显式的闭包,
而是通过将参数标记为@autoclosure来接收一个自动闭包。现在你可以将该函数当做接受String类型参数的函数来调用。
customerProvider参数将自动转化为一个闭包,因为该参数被标记了@autoclosure特性。
*/
func serveCustomer1(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer1(customersILine.removeAtIndex(0))
/*
注意:
过度使用autoclosures会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
*/
/*
@autoclosure特性暗含了@noescape特性,这个特性在非逃逸闭包一节中有描述。
如果你想让这个闭包可以“逃逸”,则应该使用@autoclosure(escaping)特性.
*/
var customerProviders: [() -> String] = []
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersILine.removeAtIndex(0))
collectCustomerProviders(customersILine.removeAtIndex(0))
print("Collected \(customerProviders.count) closures.")
// prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
import UIKit
var str = "Hello, playground"
/**
*
闭包
闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift
中的闭包与 C 和 Objective-C
中的代码块(blocks)以及其他一些编程语言中的 lambdas
函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。
这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift
会为您管理在捕获过程中涉及到的所有内存操作
在函数
章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
全局函数是一个有名字但不会捕获任何值的闭包
嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
Swift
的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
利用上下文推断参数和返回值类型
隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
参数名称缩写
尾随(Trailing)闭包语法
*/
/**
*
闭包表达式
闭包表达式是一种利用简洁语法构建内联闭包的方式。
闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
下面闭包表达式的例子通过使用几次迭代展示了sorted函数定义和语法优化的方式。
每一次迭代都用更简洁的方式描述了相同的功能。
*/
/**
* sorted函数
*/
let names = ["chris", "alex", "ewa", "daniella","barry"]
func backwards(s1:String, s2:String) -> Bool {
return s1 < s2
}
var resersed = names.sort(backwards)
print(resersed)
names.sort { (s3:String, s4:String) -> Bool in
return s3 < s4
}
//单表达式闭包隐式返回
names.sort { (s6, s7) in s6 > s7 }
//参数名缩写
resersed = names.sort({$0 > $1})
print(resersed)
/**
*
运算符函数
*/
resersed = names.sort(>)
print(resersed)
/**
*
尾随闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
*/
func someFunctionThatTakesAClosure(closure:() -> ()) {
//函数数体部分
}
someFunctionThatTakesAClosure {
//闭包主体部分
}
//注意:
如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉
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]
/*
注意
字典digitNames下标后跟着一个叹号(!),因为字典下标返回一个可选值(optional
value),表明该键不存在时会查找失败。
在上例中,由于可以确定number % 10总是digitNames字典的有效下标,因此叹号可以用于强制解包
(force-unwrap)
存储在下标的可选类型的返回值中的String类型的值。
*/
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
while number > 0{
output = digitNames[number%10]! + output
number /= 10
}
return output
}
print(strings)
/**
*
捕获值
闭包可以在其被定义的上下文中捕获常量或变量。
即使定义这些常量和变量的原作用域已经不存在,
闭包仍然可以在闭包函数体内引用和修改这些值。
*/
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0;
func incrementor()-> Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 4)
/**
该例子定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会将runningTotal变量增加10的incrementor函数。调用这个函数多次可以得到以下结果:
*/
incrementByTen()
incrementByTen()
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
/*
再次调用原来的incrementByTen会在原来的变量runningTotal上继续增加值,该变量和incrementBySeven中捕获的变量没有任何联系:
*/
incrementByTen()
/**
注意
如果您将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,您将创建一个在闭包和该实例间的循环强引用。Swift
使用捕获列表来打破这种循环强引用。更多信息,请参考闭包引起的循环强引用。
*/
/**
*
闭包是引用类型
上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。
无论您将函数或闭包赋值给一个常量还是变量,您实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。
这也意味着如果您将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
*/
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
/*
非逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape能使编译器知道这个闭包的生命周期(译者注:闭包只能在函数体中被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文),从而可以进行一些比较激进的优化。
*/
func someFunctionWithNoescape(@noescape closure: () -> Void) {
closure()
}
//例子:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure {
self.x = 100;
}
someFunctionWithNoescape {
x = 200;
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first?()
print(instance.x)
/**
*
自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
这种便利语法让你能够用一个普通的表达式来代替显式的闭包,从而省略闭包的花括号。
*/
var customersILine = ["chris","alex","ewa","barry","one"]
print(customersILine.count)//print 5
let customeProvider = {customersILine.removeAtIndex(0)}
print(customersILine.count)//print 5
print("now \(customersILine.count)")//print 5
customeProvider()
print("now now \(customersILine.count)")
/*
说明:
尽管在闭包的代码中,customersInLine的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。
如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。
请注意,customerProvider的类型不是String,而是()
-> String,一个没有参数且返回值为String的函数。
*/
//将闭包作为参数传递给函数时,
你能获得同样的延时求值行为
func serveCustomer(customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer( { customersILine.removeAtIndex(0) } )
/*
说明:
serveCustomer(_:)接受一个返回顾客名字的显式的闭包。
下面这个版本的serveCustomer(_:)完成了相同的操作,不过它并没有接受一个显式的闭包,
而是通过将参数标记为@autoclosure来接收一个自动闭包。现在你可以将该函数当做接受String类型参数的函数来调用。
customerProvider参数将自动转化为一个闭包,因为该参数被标记了@autoclosure特性。
*/
func serveCustomer1(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer1(customersILine.removeAtIndex(0))
/*
注意:
过度使用autoclosures会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
*/
/*
@autoclosure特性暗含了@noescape特性,这个特性在非逃逸闭包一节中有描述。
如果你想让这个闭包可以“逃逸”,则应该使用@autoclosure(escaping)特性.
*/
var customerProviders: [() -> String] = []
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersILine.removeAtIndex(0))
collectCustomerProviders(customersILine.removeAtIndex(0))
print("Collected \(customerProviders.count) closures.")
// prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
相关文章推荐
- 深入理解PHP之匿名函数
- Apple Swift学习教程
- 最后一次说说闭包
- 介绍 Fedora 上的 Swift
- Ruby中使用Block、Proc、lambda实现闭包
- LUA中的闭包(closure)浅析
- Lua中的闭包学习笔记
- C#中函数的创建和闭包的理解
- 深入理解javascript作用域和闭包
- javascript作用域和闭包使用详解
- 谈谈JavaScript中的函数与闭包
- 细品javascript 寻址,闭包,对象模型和相关问题
- JavaScript中的闭包原理分析
- 浅谈javascript中的闭包
- 学习javascript的闭包,原型,和匿名函数之旅
- javascript 闭包详解
- JavaScript 匿名函数和闭包介绍
- JavaScript 闭包深入理解(closure)
- 深入理解JavaScript 闭包究竟是什么