目录
Swift 是一种可以用于编写手机、桌面、服务器甚至一切可以运行代码平台应用的伟大编程语言。它安全、快速、可交互,将现代语言的最佳思想与更广泛的苹果工程文化智慧和开放源社区的各种贡献相结合。以编译器对性能进行优化,以编程语言对开发进行优化,达到兼容并蓄的效果。
Swift对新入行的程序员来说是很友好的。它是一种工业级编程语言,与脚本语言一
样具有表现力和趣味性。在Playground中编写Swift代码可以让您无需构建和运行就可以对代码进行验证并立即查看到结果。
Swift通过现代编程模型,定义了许多常见的编程错误:
变量应在使用前进行初始化
数组索引越界检查
整数一处检查
Optionals 中nil值的显示处理
内存自动管理
意外故障针对错误进行受控恢复处理
Swift通过编译、优化代码来充分发挥现代硬件设施的性能。语法与标准库的设计以明了为原则。结合了安全与速度的Swift成为了从基础到操作系统编程的绝佳语言。
Swift将强大的推理和模式匹配与现代轻质语法相结合,使复杂的想法能够以清晰简洁的方式表达,以达到易于编写、阅读、维护的目的。
Swift经过多年的发展,并将继续优化拓展。我们期待您使用它创建出光彩夺目的作品。
本文介绍了Swift5.5,这是Xcode13 中默认的Swift版本。您可以通过Xcode13来构建以Swift5.5、、Swift 4.2 或 Swift 4 编写的项目。
当您使用 Xcode 13 构建 Swift 4 和 Swift 4.2 代码时,大多数 Swift 5.5 功能都可用。也就是说,以下更改仅适用于使用 Swift 5.5 或更高版本的代码:
运行Swift 5.1时函数返回不透明类型
try表达试不会提供以返回选项以外的选项
大整数将被推断为正确的整数类型
并发需要再Swift5.5或更高的版本中使用,并提供相应的Swift标准库版本。并且在选择部署目标时,目标系统至少为iOS 15、macOS 12、tvOS 15或watchOS 8.0.
使用Swift5.5编写的项目可以依赖于使用Swift4.2 或 Swift 4编写的项目,反之亦然。这意味着,如果您有一个使用了多个框架的大项目时,你可以以框架为单位,一个个的将代码从Swift 4 迁移到Swift 5.5。
老规矩,学习新语言第一个程序应该是打印"Hello,world!"。下方代码就是Swift的实现语句:
print("Hello, world!")
如果您用过C或Objective-C写过代码,您会觉得这个语法很熟悉。在Swift中这行代码就是一个完整的程序。您不需要为输入/输出或字符串处理等功能单独倒入库。在全局范围内编写的代码用作程序的入口,因此您不需要main()函数。您也不需要在每一行代码末尾加“;”.
本教程将通过各种编程示例,为您提供Swift入门的足够信息。如果有不明了的地方,也不必担心。在本文的其余部分,将会有详细的介绍。
提醒: 为了给您更好的体验,请在Xcode中打开playground。Playgrounds可以让您编辑代码的过程中立即看到结果 下载Playground
在Swift中用var来创建变量,用let来创建常量,编译时不需要知道常量的值,但必须为常量赋值一次。就是说,在您需要多处使用,但不会改变这个值的时候,可以定义常量。
// 定义变量并初始化
var myVariable = 42
// 给变量重新赋值
myVariable = 50
// 定义常量并初始化
let myConstant = 42
分配给常量和变量的值类型时不能改变的。但您不必总是在初始化的时候定义其类型。在创建并初始化常量和变量时编译器可以根据初始化的值推断其类型。在上面的例子中,因为myVariable的初始值时一个整数,编译器将推断myVariable是一个整数。
如果初始值(或不是初始值)没有提供足够的信息,请在变量后面写上类型来指定其类型,以冒号分隔。
let explicitDouble: Double = 70
试一试1:
创建一个值为4,类型为Float的常量
值不会隐式的转换成另一种类型。如果有需要请明确的通过创建实例将类型进行转换。
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
试一试2:
尝试将最后一行中转换为String类型的操作删除。得到了什么结果
在字符串中含有变量,有一种简单的方法:将值写在括号中,在括号前面加入反斜杠,例如:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
试一试3:
在字符串中用\ ()的方式,以某人的姓名进行问候,并进行浮点型数字的计算
占用多行的字符串使用"""进行包裹。清除字符串的每一行,使其与结尾的"""对齐。例如:
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
使用方括号创建数组或者字典。在访问的时候通过在方括号中写入索引或键来获取其元素值。最后一个元素结尾可以标有“,”。
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)
在if语句中,条件必须是布尔表达式,不能是与0比较的隐式,这表示类似于if score {...}的代码是错误的。
如果值可能为空,您可以通过if和let进行处理。在类型后面写一个"?"将该值定义为可为nil的值,这样的变量可以是一个对应类型的值,页可以是一个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)"
}
试一试4:
更改上方代码中optionalName为nil。你会收到什么养的问候?添加一个else子句,为optionalNames为nil时设置不同的问候语
如果设定值为nil,那么条件表达式将返回false,并且会跳过大括号中的代码片段。如果设定值不为空,则将代码块中的代码执行后分配给常量,这使得代码块中可以使用解包值。
处理可为空的值的另外一种方式是使用"??"运算符提供默认值。如果设定的值为nil,那就使用默认值代替。代码如下:
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
switch支持各类型数据之间的各种个样的比较操作,不限于数字和想等性的比较。例如:
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.")
}
试一试5:
尝试删除default情况,看看会有什么结果
请注意如何使用let将赋值模式与常量相匹配的。
执行switch匹配的case内的代码后,程序从switch中推出。将不会继续执行下一个case内的代码,因此不需要每个case代码末尾明确跳出执行。
通过使用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 (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
试一试6:
用任意变量替换"_",跟踪最大的数字是属于哪个数组的。
使用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"
使用”..<“确定一个索引范围,可以在循环中使用这些索引值
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")
试一试7:
删除上方代码段中的"day"参数,以传参的形式在语句中添加今日特价午餐。
函数默认使用其参数名作为参数标签,也可以写入自定义参数标签,或使用“_”不实用参数标签。
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()
Function是一级类型,这代表,在一个函数中可以返回另外一个函数作为它的值。
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
})
试一试8:
重写上方的闭包函数,为所有奇数返回0
您有多种选择可以更简洁的编写闭包代码。例如在回调函数中已知闭包的类型时,您可以省略其参数类型、返回值类型。单语句的闭包将隐性的返回其唯一语句的值。
var numbers = [20, 19, 7, 12]
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
您可以通过数字来代替参数名称来使用参数。这种方法在简短的闭包中十分的便捷。当闭包作为最后一个参数传递给函数时,可以直接写在括号的后面,当闭包是函数的唯一参数时,您可以省略括号。
var numbers = [20, 19, 7, 12]
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
以class后跟类名创建类。类的常量、变量、函数、方法、属性的编写与正常方式相同,只不过它的创建时在类的上下文中。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
试一试9:
使用let添加一个常量属性,并添加一个添加参数的方法
通过类名后放置括号的形式来创建类的实例,通过点语法来访问实力的属性和方法。
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."
}
}
请注意如何使用self将属性name与初始化程序参数name区分开来。创建实例是,初始化程序的参数像函数调用一样传递。每个属性都需要分配一个值,无论是声明还是初始化参数。
如果你需要在对象被释放之前进行一些清理工作,就使用deinit创建deinitializer。
在子类的后面加上它的超类名以冒号分隔。没有规定类必须是任何根类的子类,所以您可以根据需要去继承超类。
子类中覆写超类中的实现方法都标有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()
试一试10:
创建一个NamedShape的子类Circle,将半径和名称作为初始化参数,在Circle类中实现area和simpleSescription方法。
属性除了简单的存储外,还可以有一个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 {
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"
在perimeter的setter中新值具有隐性名newValue。您可以在set之后的括号中加上显性名称。
注意到了么?EquilateralTriangle类的初始化,有三个步骤:
声明子类属性
调用初始化程序
覆写超类的属性值,在这个步骤中,可以完成对超类的方法、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
试一试11:
写一个方法通过比较他们额raw value 来对两个Rank values进行比较。
在默认的情况下,swift从0开始分配raw values,每次递增1。您也可以通过指定的显性值来更改。在上面的例子中,Ace显性的给出了raw value 1。其余的raw value 按顺序进行非配。您也可以使用字符串或者服点数作为枚举的raw value类型。使用该rawValue属性防伪枚举案例的raw value。
使用init?(rawValue:)初始化来创建一个以rawValue为原始值的枚举实例。如果匹配到了对应的raw value将会返回它的值,如果没有匹配到对应的枚举项,将会返回nil。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
枚举中case值不仅仅是原始值的另一种写入方式是有实际意义的。事实上,在一般情况下您不必提供一个没有意义的原始值。
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()
试一试12:
给Suit添加一个color方法,为黑桃和梅花返回黑色,为红心和菱形返回红色。
注意上文中枚举引用hearts的两种方式:当引用hearts常量时,枚举以其全名"Suit.hearts"进行引用,这是因为常量hearts没有明确类型。而在Suit的方法中Switch的case以.hearts的方式引用,是因为self已经知道类型时Suit了。只要已知类型,您就可以通过缩写的方式进行引用了。
如果枚举具有原始值,则原始值将作为声明的一部分,这表示枚举case中每个实例始终具有相同的原始值。枚举中case的另一种选择时让值与case相关联,这些值时在创建实例时确定的,并且每一个case的每个实例,他们都可以不同。你可以将case的关联值视作存储属性。例如,从服务器请求日出日落时间的情况,这时服务器要么以请求的信息进行相应,要么针对出错的响应内容进行描述。
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."
试一试13:
给ServerResponse添加第三个case。
要注意的是从ServerResponse中取出sunrise与sunset的方式将作为值与switch case匹配的一部分。
使用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()
试一试14:
编写一个函数,使这个函数返回一个数组,该数组包含一副完整的纸牌,包含纸牌中的每一个花色和等级的组合。
使用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
试一试15:
为ExampleProtocol添加新的需求。您需要修改SimpleClass、SimpleStructure哪些内容才能让其符合协议。
注意SimpleStructure中使用关键字mutating来修改结构中的方法。而SimpleClass不需要任何标记来对方法进行修改,因为类中的方法总是可以修改的。
extension用于向现有类添加功能,例如新的方法和计算属性。您可以使用extension为其它类型添加协议,使其与协议一致,这种扩展甚至可以调教到从库或者框架中导入的类型。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
试一试16:
为Double添加absoluteValue属性的扩展。
您可以像使用其它数据类型一样使用协议。例如:创建具有不同类型但都符合单一协议的对象的集合。当您使用协议为类型时,协议定义以外的方法将变为不可用。
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty) // Uncomment to see the error
即使运行时protocolaValue是SimpleClass类型,编译器也将视为ExampleProtocol类型。这意味着除了协议中一致的属性和方法外,您无法访问该类的其它属性和方法。
您可以使用采用了Error协议的任何类型来表示错误。
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
使用throw抛出一个错误到一个标记了可以抛出错误的方法中。如果你将一个错误从一个方法中抛出,这个方法将立即返回,并调用该方法处理错误的代码。
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"
试一试17:
将打印机名称更改为"Never Has Toner",以便函数send(job:toPrinter:)抛出错误。
您可以提供多个处理特定错误的catch代码块。catch代码的书写和switch中case的书写使用的是同一个模式。
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"
试一试18:
修改代码使其在do中抛出错误,如何修改才能执行第一个catch代码块,第二个第三个代码块又要在什么情况下执行呢?
处理错误的另一种方式是使用"try?"对结果进行转换。如果函数正常执行则返回函数的返回值,如果函数抛出错误则抛出指定的错误。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
defer用于书写执行在所有代码执行之后,仅在return之前的代码块。无论函数是否抛出错误,标记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])
试一试19:
对anyCommonElements(_:_:)函数稍作修改,使函数返回任何两个序列共有的元素数组。
写法<T:Equatable>与写法<T>...where T :Equatable的效果是一样的。
文中试一试的代码与结果文档链接:https://blog.csdn.net/ldf_tch/article/details/119003492
本文仅为个人学习参考。是在阅读文档的过程中,将文档语义转换为自己所理解的语义,无任何其它含义存在。原文地址:https://docs.swift.org/swift-book/