Closures
Swift 中的Closures與C 和Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數比較相似。
Swift’s closure expressions have a clean, clear style, with optimizations that encourage brief, clutter-free syntax in common scenarios. These optimizations include:
- Inferring parameter and return value types from context
- Implicit returns from single-expression closures
- Shorthand argument names
- Trailing closure syntax
Closure Expressions
利用簡潔語法構建內聯閉包的方式。
The Sort Method:
Swift 標準庫提供了名為sort的方法,會根據您提供的用於排序的閉包函數將已知類型數組中的值進行排序。一旦排序完成,sort(_:)
方法會返回一個與原數組大小相同,包含同類型元素且元素已正確排序的新數組。原數組不會被sort(_:)
方法修改。
ex. 使用sort(_:)方法對一個String類型的數組進行字母逆序排序.
The sorting closure needs to return true if the first value should appear before the second value, and false otherwise.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backwards(s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
Closure Expression Syntax
Closure expression syntax has the following general form:
{ (parameters) -> return type in
statements
}
Closure expression syntax可以使用常量、變量和inout類型作為參數,不能提供默認值。也可以在參數列表的最後使用可變參數。Tuples也可以作為參數和返回值。
The example below shows a closure expression version of the backwards(_:_:)
function from earlier:
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
The start of the closure’s body is introduced by the in keyword. This keyword indicates that the definition of the closure’s parameters and return type has finished, and the body of the closure is about to begin.
上述範例也可以修改成如下形式
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )
Operator Functions
There’s actually an even shorter way to write the closure expression above.
Swift 的String類型定義了關於大於(>)
的字符串實現,其作為一個函數接受兩個String類型的參數並返回Bool類型的值。而這正好與sort(_:)方法的類型一樣,所以可以改寫成如下的表達方式。
reversed = names.sort(>)
Trailing Closures
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
}
Here’s how you can use themap(_:)
method with a trailing closure to convert an array of Int values into an array of String values.
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]
如上創建了一個數字和他們英文版本名字相映射的字典。同時還定義了一個準備轉換為字符串數組的整數數組。
You can now use the numbers array to create an array of String values, by passing a closure expression to the array’s map(_:)
method as a trailing closure:
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"]
The call to the digitNames dictionary’s subscript is followed by an exclamation mark (!), because dictionary subscripts return an optional value to indicate that the dictionary lookup can fail if the key does not exist. In the example above, it is guaranteed that number % 10 will always be a valid subscript key for the digitNames dictionary, and so an exclamation mark is used to force-unwrap the String value stored in the subscript’s optional return value.
Capturing Values
A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
In Swift, the simplest form of a closure that can capture values is a nested function.
ex.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
The return type of makeIncrementer is () -> Int
. This means that it returns a function, rather than a simple value.
The makeIncrementer(forIncrement:)
function has a single Int parameter with an external name of forIncrement, and a local name of amount. The argument value passed to this parameter specifies how much runningTotal should be incremented by each time the returned incrementer function is called.
Here’s an example of makeIncrementer and calling the function multiple times shows this behavior in action:
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
If you create a second incrementer, it will have its own stored reference to a new, separate runningTotal variable, calling the original incrementer (incrementByTen) again continues to increment its own runningTotal variable, and does not affect the variable captured by incrementBySeven:
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7
incrementByTen()
// returns a value of 40
Closures Are Reference Types
上面的例子中,incrementBySeven和incrementByTen是常量,但是這些常量指向的Closure仍然可以增加其捕獲的變量的值。這是因為函數和Closure都是引用類型。
無論您將函數或閉包賦值給一個常量還是變量,您實際上都是將常量或變量的值設置為對應函數或Closure的引用。上面的例子中,指向Closure的引用incrementByTen是一個常量,而並非Closure內容本身。
Nonescaping Closures
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.
When you declare a function that takes a closure as one of its parameters, you can write @noescape
before the parameter name to indicate that the closure is not allowed to escape.
Marking a closure with @noescape
lets the compiler make more aggressive optimizations because it knows more information about the closure’s lifespan.
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
closure()
}
一種能使Closure“逃逸”出函數的方法是,將這個Closure保存在一個函數外部定義的變量中。
As an example, many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn’t called until the operation is completed—the closure needs to escape, to be called later.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
If you tried to mark the parameter of above function with @noescape
, you would get a compiler error.
Autoclosures
Autoclosures是一種自動創建的Closure,用於包裝傳遞給函數作為參數的表達式。這種閉包不接受任何參數,當它被調用的時候,會返回被包裝在其中的表達式的值。
Even though the first element of the customersInLine array is removed by the code inside the closure, the array element isn’t removed until the closure is actually called. If the closure is never called, the expression inside the closure is never evaluated, which means the array element is never removed.
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"
You get the same behavior of delayed evaluation when you pass a closure as an argument to a function.
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"
It takes an autoclosure by marking its parameter with the @autoclosure attribute.
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"
The @autoclosure attribute implies the @noescape
attribute. If you want an autoclosure that is allowed to escape, use the @autoclosure(escaping)
form of the attribute.
// 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!"
In the code above, instead of calling the closure passed to it as its customer argument, the collectCustomerProviders(_:)
function appends the closure to the customerProviders array. The array is declared outside the scope of the function, which means the closures in the array can be executed after the function returns. As a result, the value of the customer argument must be allowed to escape the function’s scope.
Enumerations
An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.
Enumeration Syntax
You introduce enumerations with the enum keyword and place their entire definition within a pair of braces:
enum SomeEnumeration {
// enumeration definition goes here
}
An example for the four main points of a compass:
enum CompassPoint {
case North
case South
case East
case West
}
Multiple cases can appear on a single line, separated by commas:
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
The type of directionToHead is inferred when it is initialized with one of the possible values of CompassPoint. Once directionToHead is declared as a CompassPoint, you can set it to a different CompassPoint value using a shorter dot syntax:
var directionToHead = CompassPoint.West
directionToHead = .East
Matching Enumeration Values with a Switch Statement
You can match individual enumeration values with a switch statement:
directionToHead = .South
switch directionToHead {
case .North:
print("Lots of planets have a north")
case .South:
print("Watch out for penguins")
case .East:
print("Where the sun rises")
case .West:
print("Where the skies are blue")
}
// prints "Watch out for penguins"
如果忽略了.West
這種情況,上面那段代碼將無法通過編譯,因為它沒有考慮到CompassPoint的全部成員。強制窮舉確保了Enumerations成員不會被意外遺漏。
當不需要匹配每個Enumerations成員的時候,你可以提供一個default分支來涵蓋所有未明確處理的枚舉成員:
let somePlanet = Planet.Earth
switch somePlanet {
case .Earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// prints "Mostly harmless"
Associated Values
You can define Swift enumerations to store associated values of any given type, and the value types can be different for each case of the enumeration if needed.
In Swift, an enumeration to define product barcodes of either type might look like this:
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
This can be read as:
“定義一個名為Barcode的enumeration type,它的一個成員值是具有(Int,Int,Int,Int)類型關聯值的UPCA,另一個成員值是具有String類型關聯值的QRCode。”
這裡的定義不提供任何Int或String類型的關聯值,它只是定義了,當Barcode常量和變量等於Barcode.UPCA或Barcode.QRCode時,可以存儲的關聯值的類型。
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
可以使用一個switch 語句來檢查不同的條形碼類型。關聯值可以被提取出來作為switch 語句的一部分。
You can place a single var or let annotation before the case name, for brevity:
switch productBarcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
print("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."
如果一個enumeration成員的所有關聯值都被提取為常量,或者都被提取為變量,為了簡潔,你可以只在成員名稱前標註一個let或者var:
switch productBarcode {
case let .UPCA(numberSystem, manufacturer, product, check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .QRCode(productCode):
print("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."
Raw Values
As an alternative to associated values, enumeration cases can come prepopulated with default values (called raw values), which are all of the same type.
Here’s an example that stores raw ASCII values alongside named enumeration cases:
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
Enumeration類型ASCII Control Character的原始值類型被定義為Character,並設置了一些比較常見的ASCII 控制字符。 Character的描述詳見字符串和字符部分。
Raw values are not the same as associated values. Raw values are set to prepopulated values when you first define the enumeration in your code, like the three ASCII codes above. The raw value for a particular enumeration case is always the same. Associated values are set when you create a new constant or variable based on one of the enumeration’s cases, and can be different each time you do so.
Implicitly Assigned Raw Values
When you’re working with enumerations that store integer or string raw values, you don’t have to explicitly assign a raw value for each case. When you don’t, Swift will automatically assign the values for you.
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
In the example above, Planet.Mercury has an explicit raw value of 1, Planet.Venus has an implicit raw value of 2, and so on(以此類推, 等等).
let sunsetDirection = CompassPoint.West.rawValue
// sunsetDirection is "West"
Initializing from a Raw Value
If you define an enumeration with a raw-value type, the enumeration automatically receives an initializer that takes a value of the raw value’s type and returns either an enumeration case or nil. You can use this initializer to try to create a new instance of the enumeration.
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type `Planet?` and equals Planet.Uranus
If you try to find a planet with a position of 9, the optional Planet value returned by the raw value initializer will be nil:
let positionToFind = 9
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .Earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// prints "There isn't a planet at position 9"
Recursive Enumerations
遞歸枚舉(recursive enumeration)是一種枚舉類型,它有一個或多個枚舉成員使用該枚舉類型的實例作為關聯值。使用遞歸枚舉時,編譯器會插入一個間接層。你可以在枚舉成員前加上indirect來表示該成員可遞歸。
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
A recursive function is a straightforward way to work with data that has a recursive structure. For example, here’s a function that evaluates an arithmetic expression:
func evaluate(expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// evaluate (5 + 4) * 2
let five = ArithmeticExpression.Number(5)
let four = ArithmeticExpression.Number(4)
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
print(evaluate(product))
// prints "18"
Classes and Structures
Classes and structures are general-purpose, flexible constructs that become the building blocks of your program’s code.
Note
An instance of a class is traditionally known as an object. However, Swift classes and structures are much closer in functionality than in other languages, and much of this chapter describes functionality that can apply to instances of either a class or a structure type. Because of this, the more general term instance is used.
Comparing Classes and Structures
Classes and structures in Swift have many things in common. Both can:
* Define properties to store values
* Define methods to provide functionality
* Define subscripts to provide access to their values using subscript syntax
* Define initializers to set up their initial state
* Be extended to expand their functionality beyond a default implementation
* Conform to protocols to provide standard functionality of a certain kind
Classes have additional capabilities that structures do not:
* Inheritance enables one class to inherit the characteristics of another.
* Type casting enables you to check and interpret the type of a class instance at runtime.
* Deinitializers enable an instance of a class to free up any resources it has assigned.
* Reference counting allows more than one reference to a class instance.
Definition Syntax
Classes and structures have a similar definition syntax. You introduce classes with the class keyword and structures with the struct keyword.
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
Note
Whenever you define a new class or structure, you effectively define a brand new Swift type. Give types UpperCamelCase names (such as SomeClass and SomeStructure here) to match the capitalization of standard Swift types (such as String, Int, and Bool).
Conversely, always give properties and methods lowerCamelCase names (such as frameRate and incrementCount) to differentiate them from type names.
ex.
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
Class and Structure Instances
The Resolution structure definition and the VideoMode class definition only describe what a Resolution or VideoMode will look like.
They themselves do not describe a specific resolution or video mode. To do that, you need to create an instance of the structure or class.
let someResolution = Resolution()
let someVideoMode = VideoMode()
通過這種方式所創建的類或者結構體實例,其屬性均會被初始化為默認值。
Accessing Properties
You can access the properties of an instance using dot syntax
. In dot syntax, you write the property name immediately after the instance name, separated by a period (.)
, without any spaces:
print("The width of someResolution is \(someResolution.width)")
// prints "The width of someResolution is 0"
Unlike structures, class instances do not receive a default memberwise initializer.
Memberwise Initializers for Structure Types
All structures have an automatically-generated memberwise initializer, which you can use to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name:
let vga = Resolution(width: 640, height: 480)
Structures and Enumerations Are Value Types
A value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function.
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print("cinema is now \(cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
print("hd is still \(hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
在將hd賦予給cinema的時候,實際上是將hd中所存儲的值進行拷貝,然後將拷貝的數據存儲到新的cinema實例中。結果就是兩個完全獨立的實例碰巧包含有相同的數值。由於兩者相互獨立,因此將cinema的width修改為2048並不會影響hd中的width的值。
Classes Are Reference Types
Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function.
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
//Assigned to a new constant
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 输出 "The frameRate property of theEighty is now 30.0"
因為類是Reference Type,所以tenEight和alsoTenEight實際上引用的是相同的VideoMode實例。換句話說,它們是同一個實例的兩種叫法。
Note that tenEighty and alsoTenEighty are declared as constants, rather than variables. However, you can still change tenEighty.frameRate and alsoTenEighty.frameRate because the values of the tenEighty and alsoTenEighty constants themselves do not actually change. tenEighty and alsoTenEighty themselves do not “store” the VideoMode instance—instead, they both refer to a VideoMode instance behind the scenes. It is the frameRate property of the underlying VideoMode that is changed, not the values of the constant references to that VideoMode.
Properties
Properties associate values with a particular class, structure, or enumeration.
Stored Properties
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
Stored Properties of Constant Structure Instances
如果創建了一個結構體的實例並將其賦值給一個常量,則無法修改該實例的任何屬性,即使定義了變量存儲屬性.
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
Because rangeOfFourItems is declared as a constant (with the let keyword), it is not possible to change its firstValue property, even though firstValue is a variable property.
Lazy Stored Properties
A lazy stored property is a property whose initial value is not calculated until the first time it is used.
Note
You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.
Lazy properties are useful when the initial value for a property is dependent on outside factors whose values are not known until after an instance’s initialization is complete. Lazy properties are also useful when the initial value for a property requires complex or computationally expensive setup that should not be performed unless or until it is needed.
The example below uses a lazy stored property to avoid unnecessary initialization of a complex class.
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a non-trivial amount of time to initialize.
*/
var fileName = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created
print(manager.importer.fileName)
// the DataImporter instance for the importer property has now been created
// prints "data.txt"
If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
Computed Properties
In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// prints "square.origin is now at (10.0, 10.0)"
這個例子定義了3 個結構體來描述幾何形狀:
* Point封裝了一個(x, y)的坐標
* Size封裝了一個width和一個height
* Rect表示一個有原點和尺寸的矩形
The self Property
Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself.
可以在一個實例的實例方法中使用這個隱含的self屬性來引用當前實例。
lass Counter {
var count = 0
func increment() {
++count
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
func increment() {
self.count++
}
Here, self disambiguates between a method parameter called x and an instance property that is also called x:
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
print("This point is to the right of the line where x == 1.0")
}
// prints "This point is to the right of the line where x == 1.0"
Without the self prefix, Swift would assume that both uses of x referred to the method parameter called x.
參考資料:
1. https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0
2. http://wiki.jikexueyuan.com/project/swift/
沒有留言:
張貼留言