About Swift — The Swift Programming Language (Swift 5.6)
欢迎运用swift
关于swift
swift是一门极好的编写软件的言语, 不管是用来写手机软件、桌面软件、服务器或许是其他跑代码的东西。它是安全、快速、交互的编程言语, 在现代编程言语中结合了最好的、来自于广泛的苹果工程文明和其开源社区的各种奉献。编译器为功用进行了优化, 言语为开发做了优化, 不将就。
swift对于新手来说是友好的。它是一门具有工业品质的编程言语, 同时也是富有表现力和令人愉快的脚本言语。在playground编写swfit让你体会编程并能够马上看到成果, 不需求build和run的开销
swift经过选用现代编程形式, 避免了常规编程的常见过错
- 变量永远在运用之前初始化
- 数组的索引会做越界检查
- Integer会做溢出检查
- 可选项确保nil会被显现处理
- 自动内存办理
- 过错处理答应从反常中进行可控的恢复
swift代码为了能充分利用现代硬件,是编译和优化过的。语法和标准库被规划建立在引导原则上, 依照提示的办法去书写你的代码得到最佳运转效果。它结合了安全和速度, 是从hello world到完好的可操作系统的构建的不错的挑选
swift结合了强大的类型接口和现代的、轻量级的语法、答应杂乱的主意以简洁明了的办法完成的形式. 代码不单单更简略书写并且更简略阅览和掌握
swift现已开展多年, 也在持续地进化出新功用和才能。咱们对于swift充满信心。现已等不及看你运用swift进行创作了。
版别兼容性
书中描绘的是swift5.6, XCode13默认的swift版别, 你也能够运用XCode13构建swift4.2或许swift4。当你运用XCode13构建swift4、swift4.2的代码时, 多数的swift5.6的功用是可用的。以下改变只针对运用swift5.6及以后的代码有用
- 回来不透明类型的函数需求swift5.1 runtime
-
try?
对于现已回来可选项的表达式不再需求引进一个可选值 - large integer初始化的时分会揣度为正确的integer类型
并发需求swift5.6或之后的版别, 以及一个供给了应对并发类型的swift标准库。在苹果平台, 设置方针运转环境最低为iOS15、macOS12、tvOS15 或许 watchOS8.0
一个swift5.6的项目能够依赖于一个swift4.2或许swift4的项目, 反之亦然。假如你存在一个大的项目区分成了多个frameworks, 你能够一次将代码从swift4.0迁移到swift5.6
总览
Hello World
print("Hello, world!")
// Prints "Hello, world!"
这一行代码直接就能履行
不需求为输入输出功用或许字符串处理导入单独的一个库。在全局规模编写的代码作为程序的入口, 所以也不需求main(), 你也不需求在每句代码后边加分号
- 建议合作playground运用这一章节, 它能够让你在写代码的时分就看见成果
简略的值
let标识常量, var标识变量。常量的值在编译的时分不需求被知道它的类型, 但你有必要声明时给它赋值。你能够声明一个常量然后在其他当地运用
var myVariable = 42
myVariable = 50
let myConstant = 42
常量和变量有必要和你想给它赋的值是同一个类型。你不需求显式地指定类型, 在你给他们赋值的时分编译器会指定他们的类型。
假如初始值不能供给足够的信息或许没有初始值, 在变量名后边加个冒号, 显式指定类型
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
值不会隐式地转化为其他类型, 你需求操作不同类型的值的时分需求显现转化
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
对于字符串来说有愈加简便的转化办法, 字符串内运用\加括号包裹值
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
运用三个引号”””包裹住string, string能够占多行, 每个引证行的最初缩进被移除, 只需匹配右引号的锁进
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) 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")
print(shoppingList)
运用初始化器语法创立一个空的字典或许数组
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
假如类型信息能够被揣度出来, 你能够用以下的办法去写一个空的数组、空的字典,
shoppingList = []
occupations = [:]
控制流
运用if
和switch
去构建条件句子, 运用for-in
、while
和repeat-while
去构建循环。运用圆括号去包裹条件或许循环变量是可选的, 主体内容的大阔号是有必要的
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// Prints "11"
在if句子傍边, 条件值有必要是布尔值表达式, 这意味着if score {...}
这样的代码是过错的, 它不会将其和0值进行比较。
你能够运用if和let一同处理或许缺失的值, 这个或许缺失的值是一个可选项, 一个可选项的值能够是一个值, 也能够是nil表明这个值是缺失的。在变量的类型后边加上问号表明这个变量是一个可选项。
var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
假如可选项为nil, 上面的条件值是false, 大括号里边的代码不会履行。假如可选项不为nil, 则会被解包并赋值给let常量, 在大括号内, 这个let常量是有用的。
别的一种处理可选项的办法是运用??供给一个默认值。假如可选项的值是缺失的, 默认的值会替代被运用
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
switch支撑任何类型的数据和广泛品种的比较操作。不再约束成integer, 而是检测值是否持平
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.")
}
// Prints "Is it a spicy red pepper?"
留意上面let常量是如安在形式中被运用的, 用来将一个匹配形式的值赋值给常量。
履行完switch内某一个匹配的switch之后, 程序会从switch句子中退出, 不会持续履行下一个case, 你不再需求显现地去在每一个case的末尾break;
你能够运用for-in
去遍历字典里边的每个item, 经过供给一对姓名去运用每一个键值对。字典是一个无序的调集, 所以键值对依照一个任意的顺序被遍历。
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 (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
// Prints "25"
运用while
去履行代码直到条件产生改动, 也能够将while
放到后边, 确保循环体至少履行一次
var n = 2
while n < 100 {
n *= 2
}
print(n)
// Prints "128"
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
// Prints "128"
有index的循环, 合作..<
约束规模
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// Prints "6"
..<
表明不包括右边的值, ...
表明包括右边的值
函数和闭包
运用func
声明一个函数, 运用函数的时分, 办法名 + 括号包裹的参数列表。在声明一个函数的时分, 运用->
切割参数列表和回来类型
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
默认的函数运用参数名作为参数外部标签, 你能够在参数名前面设置自界说的参数标签, 也能够运用_
表明不需求外部标签
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
运用元组去创立一个组合值, 比方, 从一个函数回来多个值。元组的值能够经过姓名或许序号获取。
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
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)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"
函数能够被嵌套, 嵌套的函数能够拜访在函数外部的变量, 你能够运用嵌套函数去组织较长的或许杂乱函数的代码
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
函数是first-class类型, 一个函数能够作为一个函数的回来值
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
一个函数能够是另一个函数的参数
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)
函数是特别类型的闭包, 闭包是一段能够在之后被调用的代码, 闭包内的代码能够拜访到闭包创立环境下的变量和函数, 即便闭包在别的一个彻底不同的环境中被履行,比方上边的嵌套函数。你能够经过用花括号包裹去写一个匿名闭包, 运用in
去切割参数和回来类型 以及 闭包主体
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
你有好几种办法去简化闭包。当一个闭包的类型现已被承认, 比方delegate的回调, 你能够省掉参数的类型, 或许回来值的类型, 或许都省掉。单个句子的闭包会隐式地回来这个仅有的句子的值
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"
你能够经过数字替代参数名去拜访参数, 这种办法对于小的闭包来说是非常有用的。一个闭包假如是函数的最终一个参数, 能够直接跟在参数列表圆括号后边。假如是仅有的参数, 那么圆括号能够省掉
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"
对象和类
运用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()
类创立一个实例的时分会运用初始化办法init
进行设置
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
留意上边在init办法里边经过self. 区分了特点和初始化的参数name。每一个特点都需求初始值, 能够在声明特点的时分给到, 也能够在初始化器里边给到。
运用deinit
去创立一个去初始化器, 假如你需求在对象deallocated之前做一些整理。
子类需求在自己的class姓名后跟上父类的姓名, 运用冒号:分隔。没有必要让一个类显式声明为某一标准根类的子类, 你能够依照自己的需求增加或许省掉父类声明。
子类的办法假如需求覆盖父类的完成, 需求运用override
标识。没有运用override标识意外覆盖了父类的办法会被编译器检测为一个过错, 编译器也会检测到运用了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()
除了简略的存储性特点, 特点能够有getter和setter办法
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 {
sideLength = newValue / 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)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"
在setter傍边, newValue作为隐式的传入的参数名, 你能够运用()跟在set后边供给一个显式的名称
在上边等边三角形的初始化傍边有3个不同的过程:
-
设置子类声明的特点值
-
调用父类的初始化办法
-
改动父类傍边的界说的特点的值, 其他额定的运用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)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"
当运用可选项的时分, 能够在可选项的办法、特点、下标之前写上?。假如?前面值是nil, 任何?后边的都会被疏忽, 并且整个表达式的值是nil。不写上?的话, 可选项会被解包, 之后的一切?作用于被解包的值。两种办法来说, 整个表达式都是可选项.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
枚举和结构体
运用enum
发明枚举, 像类和其他命名类型相同, 枚举能够有自己相关的办法
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
默认地, swift枚举初始值从0开端顺次增大, 可是你能够经过显式地指定值去改动这一点。上面的类型傍边运用1作为Ace的初始值, 剩下的初始值顺次增大。你也能够运用字符串或许浮点数作为枚举的初始值。运用rawValue
特点去拜访一个枚举项的初始值。
运用init?(rawValue:)
初始化器去用一个初始值发明一个枚举实例。回来对应值的枚举项, 或许没有对应枚举项, 回来nil
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
枚举项的值便是真实的值, 不单单只是另一种表明他们初始值的办法。假如有没有意义的初始值, 也没有必要供给一个枚举项表明它。
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的枚举项的两种推导办法, 将hearts枚举项给到常量的时分, 运用的是Suit.hearts, 由于常量没有显式指定类型。而在枚举内部的Description傍边, 直接运用了.hearts, 由于self现已被认定为是Suit的枚举。在现已知道类型的情况下, 你能够运用缩写。
假如一个枚举设定了初始值, 这些值被确定为声明的一部分, 这就意味着每一种枚举项的实例都是相同的初始值。别的一种办法便是让枚举项和值进行相关, 只要在你创立实例的时分才会确定, 同一枚举项的值能够是不同的。你能够以为相关的值类似于枚举项实例的特点。比方, 从服务器恳求日出、日落时刻, 服务器要么同时回来这两个时刻, 要么回来犯错信息
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):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."
运用struct
去创立一个结构体, 结构体支撑许多和类相同的行为, 包括类和初始化器。结构体和类之间最大的区别便是, 对于赋值来说, 结构体是做值拷贝, 可是类是经过引证计数, 传递指针。
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()
协议和扩展
运用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
, 去标识一个将会修改结构体的办法。而类不需求这个标识, 由于类傍边的办法总是能够修改类。
运用extension
去对现已存在的类型增加功用, 比方新的办法和核算型特点。你能够运用扩展对声明在任何其他位置的类型增加协议的完成, 甚至是你从库或许framework傍边导入的类型。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
像其他命名类型相同, 你能够运用协议名表明一个类型, 比方创立一个类型不同可是遵从同一协议的调集。可是当你操作这些类型为协议类型的对象的时分, 协议以外的办法是不能够运用的
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty) // Uncomment to see the error
虽然上面的protocolValue运转时类型是SimpleClass, 可是编译器会将其作为ExampleProtocoll来处理, 这意味着你不能拜访除了在协议里边规定的特点和办法
过错处理
你能够运用遵从了Error
协议的任何类型来代表errors
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的代码块傍边, 你需求在或许抛出过错的代码前面标识try
。在catch代码块傍边, 过错会运用error作为过错名
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// Prints "Job sent"
你能够供给多个catch代码块去处理特定的过错, 一个catch紧接上一个catch, 类似于switch
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)
}
// Prints "Job sent"
别的一种处理过错的办法是运用try?
去将成果转化为可选项。假如抛出过错, 过错会被丢掉, 回来值是nil, 否则回来值是一个包括了函数回来值的可选项。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
运用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)
// Prints "false"
泛型
运用尖括号去构造一个范型函数或许类型
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)
函数、办法、类、枚举和结构体都支撑范型
// Reimplement the Swift standard library's optional type
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])
写<T: Equatable>
是等价于<T>... where T: Equatable