Swift学习
The Swift Programming Language
参考文献:Swift GuidedTour
根据官方文档进行学习。
GuidedTour
print("Hello, world!")
这样就可以打印出Hello, world!
##Simple Value
Use let to make a constant and var to make a variable.
一个constant值可以不在编译阶段就确定,不过只能对其赋值一次。
一个constant和variable在赋值阶段,需要对其数值类型进行确定。若指定,则使用默认值;若指定,则使用指定类型。
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
得到结果有:
implicitInteger: Int = 70
implicitDouble: Double = 70
explicitDouble: Double = 70
一个变量的数值类型不会暗中进行改变,如果要将它变为其他类型,需要明确指出。例如:
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
有一个更简便的方法将一个值指定为string类型。将该值放在小括号中,并在小括号前加上反斜杆。例如:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
结果为:
apples: Int = 3
oranges: Int = 5
appleSummary: String = "I have 3 apples."
fruitSummary: String = "I have 8 pieces of fruit."
使用三个双引号(""")的标记,可以赋值多行的字符串。在多行字符串前后都加上这个标记,即可赋值。例如:
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
结果为:
quotation: String = "I said \"I have 3 apples.\"\nAnd then I said \"I have 8 pieces of fruit.\""
创建数组或者字典使用中括号[]。通过index或者key来检索元素值。最后一个元素后面加逗号是被允许的。例如:
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
shoppingList.append("blue paint")
结果是:
shoppingList: [String] = 4 values {
[0] = "catfish"
[1] = "bottle of water"
[2] = "tulips"
[3] = "blue paint"
}
occupations: [String : String] = 3 key/value pairs {
[0] = {
key = "Jayne"
value = "Public Relations"
}
[1] = {
key = "Malcolm"
value = "Captain"
}
[2] = {
key = "Kaylee"
value = "Mechanic"
}
}
创建空数组或字典,可以使用如下初始化语法:
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
若类型已知(类型已被限定),则可以使用:
shoppingList = []
occupations = [:]
Control Flow(控制模块)
Use if and switch to make conditionals, and use for-in, while, and repeat-while to make loops.
for循环例子:
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
结果:
individualScores: [Int] = 5 values {
[0] = 75
[1] = 43
[2] = 103
[3] = 87
[4] = 12
}
teamScore: Int = 11
可选类型(Optionals)
var optionalInteger: Int? //在类型和 ?之间没有空格
类似这种在类型定义后加个问号,表示变量为可选类型。可选类型类型是一个两种情况的枚举,None和Some(T),表示可能有值,可能没有值。若可选类型的变量包含值,可使用操作符!来访问该值;否则使用!会导致运行时的错误。
强制解析
var myString:String?
myString = "Hello, Swift!"
if myString != nil {
// 强制解析
print( myString! )
}else{
print("myString 值为 nil")
}
//结果:Hello, Swift!
自动解析
var myString:String!
myString = "Hello, Swift!"
if myString != nil {
print(myString)
}else{
print("myString 值为 nil")
}
//结果:Hello, Swift!
可选绑定
可选绑定可以用在if和while中,对可选类型的值进行判断并赋值给一个常量或变量。例如:
var myString:String?
myString = "Hello, Swift!"
if let yourString = myString {
print("你的字符串值为 - \(yourString)")
}else{
print("你的字符串没有值")
}
//结果:你的字符串值为 - Hello, Swift!
可选判定式
如下例子。nickName是可选类型的常量,可以使用??对其进行判断,如果判断出nickName为nil,则使用后面的默认值。
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
结果
nickName: String? = nil
fullName: String = "John Appleseed"
informalGreeting: String = "Hi John Appleseed"
switch 举例
可以从中看到let的另一个用法。用其来判断switch的值是否满足某一个规则。在case被满足后,程序会退出switch块。所以无需在每个case后加break。
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
结果:
Is it a spicy red pepper?
for-in的用法
for-in可以对一个数组或字典进行迭代。
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
print(kind)
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
结果:
Square
Fibonacci
Prime
25
while 和 repeat的使用
var n = 2
while n < 100 {
n *= 2
}
print(n)
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
结果:
128
128
可以使用…<和…来创建一个索引范围,并以此进行循环取值。例:
…< 表示不包括最大值;…表示包括最大值。
var total = 0
for i in 0..<4 {
total += i
}
print(total)
//结果:6
var total = 0
for i in 0...4 {
total += i
}
print(total)
//结果:10
函数和闭包
使用func来声名一个函数。通过函数名,并包含在括号内的参数来调用函数。使用->来区分(参数名称&类型)与函数返回类型。例:
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
//返回值: $R1: String = "Hello Bob, today is Tuesday."
一般情况下,函数使用参数名来作为参数的标签,不过还可以在参数名前面添加自定义标签;或者在参数名前添加**_**表示无参数标签。例:
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
//_表示无参数标签,所以可以不写person;on表示day的自定义标签。
###元组–返回不同类型的多个值
函数的返回类型可以是不同类型的多个值,此时就需要使用到元组。
元组与数组类似,不同的是,元组中的元素可以是任意类型,使用的是圆括号。例:
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int, name:String) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum, "HAHA")
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
print(statistics.3)
结果:
120
120
HAHA
嵌套函数
例1:
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
//返回值:15
例2:
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
//返回值:8
一个函数可以作为另一个函数的参数。例:
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
//返回:$R11: Bool = true
闭包
函数实际上是闭包的一种特殊情况:可以稍后调用的代码块。闭包中的代码可以访问在创建闭包的范围内可用的变量和函数等内容,即使在执行闭包时它位于不同的范围内—您已经看到了一个嵌套函数的例子。您可以通过使用大括号({})包围代码来编写没有名称的闭包。使用in将参数和返回类型与正文分隔开(Use in to separate the arguments and return type from the body.)。
以下:numbers = [20, 19, 12, 7]
例1与例2等同;
例1:
func mapTest(number: Int) -> Int {
let result = 3 * number
return result
}
numbers.map(mapTest);
例2:
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
闭包中,当类型已知时,可以省略类型的说明。例3就是例2的等同,但是省略了类型的说明。
例3:
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
可以使用$0、$1等来对应表示闭包的第一个参数值与第二个参数值等。当参数只有闭包时,可以省略圆括号。
例4:
let mappedNumbers = numbers.map({$0 * 3 })
//或者 let mappedNumbers = numbers.map{$0 * 3 }
print(mappedNumbers)
//等同于例3
例5:
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
对象和类
class后跟着类名,来创建一个类。常量、变量和方法的声明还是照常,不过需要在class的上下文中。
例:
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
结果:
shape: Shape = {
numberOfSides = 7
}
shapeDescription: String = "A shape with 7 sides."
添加初始化方法是重要的,在函数创建对象后可以进行初始化。(初始化init, 反初始化deinit)
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
//self表示对象自身
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
也拥有子类与父类的概念。子类继承父类,中间用冒号**?*隔开。子类可以覆盖父类的方法,使用override对方法进行标记,可以让编译器对方法进行检查,查看是否正确覆盖。例:
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
结果:
$R14: String = "A square with sides of length 5.2."
test: Square = {
__lldb_expr_163.NamedShape = {
numberOfSides = 4
name = "my test square"
}
sideLength = 5.2000000000000002
}
类的属性可以拥有setter和getter。例如:
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set(aTest) {
sideLength = aTest / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
结果:
9.3
3.3000000000000003
在set中,如果没有设置aTest这个形参,那么将会默认形参为newValue.
可以看到,初始化遵循三个步骤:
- 设置子类声明的属性值。
- 调用超类的初始化器。
- 更改超类定义的属性的值。任何使用方法、getter或setter的附加设置工作也可以在此时完成。
可以在对象初始化前后,进行监听。监听方法是willSet与didSet。例:
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
结果:
10.0
10.0
50.0
可选类型在对象上的表现:
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
一个对象是可选的(optionalSquare),那么(optionalSquare?.sideLength)整个表达式都是可选的。即sideLength也是可选类型。
Enumerations and Structures(枚举和结构)
枚举
使用enum来创建枚举类型。枚举类型可以拥有内部方法。
若指定枚举的类型为Int,则默认枚举值从0逐一递增;也可指定某一枚举值为1(或其他整型数),则其他枚举值从1递增。使用rawValue来查看枚举情况所对应的值。初始化方法init?(rawValue:)会返回可选类型。
例1:
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
let three = Rank.three
let threeDescription = three.simpleDescription()
例1结果:
ace: Rank = ace
aceRawValue: Int = 1
three: Rank = three
threeDescription: String = "3"
例2, 初始化方法的使用:
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
枚举的值在没有意义的情况下,也不是必须的。如下例,就没有rawValue.
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
结果:
hearts: Suit = hearts
heartsDescription: String = "hearts"
可以注意到,switch下方有.hearts的简写情况,这是因为已经知道枚举的类型情况下,可以省略枚举类的说明。所以此处.hearts等同于Suit.hearts
枚举用例的值可以看成绑定在枚举用例上的属性。下方是对枚举使用的扩展:
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
//等同于 case .result(let sunrise, let sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
结果:
Sunrise is at 6:00 am and sunset is at 8:09 pm.
success: ServerResponse = result {
result = {
0 = "6:00 am"
1 = "8:09 pm"
}
}
failure: ServerResponse = failure {
failure = "Out of cheese."
}
结构体
使用struct来创建结构体。结构体与class相似,拥有方法与构造器。与class不同的是,结构体在代码中传递都是通过复制来实现;而class则是通过传递引用来实现。
例子:
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
结果:
threeOfSpades: Card = {
rank = three
suit = spades
}
threeOfSpadesDescription: String = "The 3 of spades"
Protocols and Extensions(协议和扩展)
使用protocol来声明一个协议。例如:
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
类、枚举、结构体都可以采用协议。例如:
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
mutating所对应的方法让结构体可以更改属性。结果:
a: SimpleClass = {
simpleDescription = "A very simple class. Now 100% adjusted."
anotherProperty = 69105
}
aDescription: String = "A very simple class. Now 100% adjusted."
b: SimpleStructure = {
simpleDescription = "A simple structure (adjusted)"
}
bDescription: String = "A simple structure (adjusted)"
使用extension来扩展类。无论此类来自于哪里(第三方包也可)。并可在扩展中实现协议。
extension Int:ExampleProtocol{
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
结果:
The number 7
一个变量,定义类型为协议,则可为其赋值为实现该协议的任意类的实例。比如:尽管实例a的类型为SimpleClass,但是可以将其赋值到ExampleProtocol的类型的变量。
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
结果:
A very simple class. Now 100% adjusted
Error Handling(异常捕获)
任意采用了Error协议的types,都可以用来表示异常。
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
使用throw来抛出异常,使用throws来标记一个函数拥有抛出异常的能力。在方法内抛出异常,则此方法立即返回,并且调用此方法的代码捕获了此异常。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
do-catch
有许多方法可以捕获异常。其中一种是do-catch,在do块内,使用try标记可能抛出异常的代码;在catch中,除非你对异常指定其他名字,否则异常自动被命名为error。
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
可以使用多catch来获取异常,以区别不同的异常情况。
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
try?
也可以使用try?来处理异常。使用此种方式,会得到一个可选类型,若方法抛出异常,则丢弃此异常,并返回nil,否则返回正确值,并生成一个可选类型。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
结果:
printerSuccess: String? = "Job sent"
printerFailure: String? = nil
defer标记的使用
defer对应的块会在方法中其他代码都执行后且方法返回前才去执行。无法方法是否抛出异常,该代码都会执行。可以使用defer对代码进行设置及清空。就算使这两个代码紧邻,他们也会在不同时间执行。
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
结果:
false
Generics(泛型)
在尖括号中设置一个名字,用来表示方法或者type的泛型。
例:
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
结果:
$R0: [String] = 4 values {
[0] = "knock"
[1] = "knock"
[2] = "knock"
[3] = "knock"
}
泛型可以用于方法、类、枚举、结构体中。
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
对泛型进行限定,可以使用where和冒号。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
结果:
$R1: Bool = true
<T: Equatable> 与 <T> ... where T: Equatable 一致.