[翻译]Swift导览

Swift导览

第一次用一门新语言编程,通常要在屏幕上打印“Hello,World”。用Swfit,一行就可以搞定:

​println​(​"Hello,world!"​)

如果你写过C或者Objective-C的代码,上面的语法看来是是这么熟悉——在Swift中,这行代码就是一个完整的程序了。你不需要为了输入/输出功能或者字符串处理而导入一个特定的库。写在全局域的代码被当作程序的入口,所以你不需要一个main函数。也不需要在每条语句的末尾写分号作为结尾。

本章会通过展示如何完成一系列变成任务,来给你的Swift编程之旅开个好头。如果你对某些东西不理解请不必担心——本章介绍的所有内容都会在本书的后续内容中有详细的解释。

NOTE

为了有更好的体验,可以在Xcode中的playground打开本章的代码。playgrund能够让你编辑代码的后马上看到结果。
在Mac电脑上,下载playground,双击连接 https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.playground.zip 用Xcode将其打开。

简单类型

分别使用let和var定义常量和变量。在编译阶段,常量不需要有值,但是必须要给它赋值一次。也就是说常量只能赋值一次但是可以多次使用。

​var​ ​myVariable​ = ​42
​myVariable​ = ​50
​let​ ​myConstant​ = ​42

常量值和变量值必须与你赋值的类型相同。但是你不必每次都要填写具体的类型。在创建一个常量或者变量时为其提供一个值就行了,编译器能够推测出它的正确类型。在上面的例子中,因为myVariable的初始值是一个整型,所以编译器推测它的类型是整型。

如果初始值没有提供足够的类型信息(或如果没有初始值),需要在(变量名称)后面写上它的类型,用冒号做分隔开来。

​let​ ​implicitInteger​ = ​70
​let​ ​implicitDouble​ = ​70.0
​let​ ​explicitDouble​: ​Double​ = ​70

实验
创建一个类型为Float值为4的常量。

Swift中的值不会隐式转换类型。如果你需要将一个值转换为其他类型,需要用想要的类型明确的标记那个值。

let​ ​label​ = ​"The width is "
​let​ ​width​ = ​94
​let​ ​widthLabel​ = ​label​ + ​String​(​width​)

实验
试着将Stirng从最后一行移除。你会得到什么错误?

有一种简单的方式将值引入到字符串中:用圆括号包裹值,并在括号前面写上一个反斜线(\)。比如:

​let​ ​apples​ = ​3
​let​ ​oranges​ = ​5
​let​ ​appleSummary​ = ​"I have ​\(​apples​)​ apples."
​let​ ​fruitSummary​ = ​"I have ​\(​apples​ + ​oranges​)​ pieces of fruit."

实验
使用()将浮点计算的结果放置引入到字符串中并加上某个人的名字向其打个招呼。

用一对方括号([])就能创建数组和字典,通过在方括号中的索引和键就能访问它们中的元素。

var​ ​shoppingList​ = [​"catfish"​,​"water"​,​"tulips"​,​"blue paint"​]
​shoppingList​[​1​] = ​"bottle of water"
​
​var​ ​occupations​ = [
​    ​"Malcolm"​: ​"Captain"​,​    ​"Kaylee"​: ​"Mechanic"​,​]
​occupations​[​"Jayne"​] = ​"Public Relations"

为了创建空的数组和字典,需要用构造语法。

​let​ ​emptyArray​ = [​String​]()
​let​ ​emptyDictionary​ = [​String​: ​Float​]()

如果类型是可以推测出来的,可以用[]创建一个空的数组,用[:]创建一个空的字典——比如当给变量设置值或者给函数传参。

​shoppingList​ = []
​occupations​ = [:]

控制流

使用if和switch创建条件语句,使用for-in、for、while和do-while创建循环语句。包裹条件或循环变量的圆括号是可选的,但包裹条件和循环体的花括号缺是必须的。

​let​ ​individualScores​ = [​75​,​43​,​103​,​87​,​12​]
​var​ ​teamScore​ = ​0
​for​ ​score​ ​in​ ​individualScores​ {
​    ​if​ ​score​ > ​50​ {
​        ​teamScore​ += ​3
​    } ​else​ {
​        ​teamScore​ += ​1
​    }
​}
​teamScore

NOTE
在上面的代码中,teamScore被单独写在一行。这是在playground中查看一个变量的值的简单做法。

在if语句中,条件必须是一个布尔值表达式——这就意味着像“if score { … }”这样的语句是错误的,与0的比较是不明确的(译者:这是在影射某些语言啊)。

你可以将if和let一起使用来处理可能缺失的值。这些值用可选类型表示。一个可选类型的值要末包含一个值,要末包含一个nil,表示值是缺失的。在一个值的类型之后写上一个问号(?)就标记那个值是可选的了。

var​ ​optionalString​: ​String​? = ​"Hello"
​optionalString​ == ​nil
​
​var​ ​optionalName​: ​String​? = ​"John Appleseed"
​var​ ​greeting​ = ​"Hello!"
​if​ ​let​ ​name​ = ​optionalName​ {
​    ​greeting​ = ​"Hello,​\(​name​)​"
​}

实验

将optionalName 置为nil。你会得到什么问候?添加一个else从句为 optionalName 是nil的时候设置一个不同的问候。

如果可选类型值是nil,if语句的条件结果是false,花括号的内容被忽略了。否则,可选类型值被拆包后赋值给let后的常量,这样让拆包后的值可以在代码块内可以被使用。

Switch支持任何类型的数据和多种比较操作符——没有一定是整型的限制和一定是想等的限制。

let​ ​vegetable​ = ​"red pepper"
​switch​ ​vegetable​ {
​case​ ​"celery"​:
​    ​let​ ​vegetableComment​ = ​"Add some raisins and make ants on a log."
​case​ ​"cucumber"​,​"watercress"​:
​    ​let​ ​vegetableComment​ = ​"That would make a good tea sandwich."
​case​ ​let​ ​x​ ​where​ ​x​.​hasSuffix​(​"pepper"​):
​    ​let​ ​vegetableComment​ = ​"Is it a spicy ​\(​x​)​?"
​default​:
​    ​let​ ​vegetableComment​ = ​"Everything tastes good in soup."
​}

实验
尝试移除defalut case ,你会得到什么错误?

记住let是如何在一个模式中被使用的:将和模式的一部分匹配的那个值分配给一个常量。

在执行完匹配的case中的代码后,就会离开switch语句。而不会继续执行下一个case,所以这里不必在每个case的结束写上break。

你可以使用for-in 遍历一个字典中的元素,通过提供为每个键值对提供一对名字。字典是无序的集合,所以遍历过程中他们的键值出现的顺序是随机的。

let​ ​interestingNumbers​ = [
​    ​"Prime"​: [​2​,​3​,​5​,​7​,​11​,​13​],​    ​"Fibonacci"​: [​1​,​1​,​2​,​8​],​    ​"Square"​: [​1​,​4​,​9​,​16​,​25​],​]
​var​ ​largest​ = ​0
​for​ (​kind​,​numbers​) ​in​ ​interestingNumbers​ {
​    ​for​ ​number​ ​in​ ​numbers​ {
​        ​if​ ​number​ > ​largest​ {
​            ​largest​ = ​number
​        }
​    }
​}
​largest

实验
添加一个变量来跟踪是哪种类型的数字是最大的,也就是最大的数字是哪个类型的。

使用while来重复执行一个代码块直到条件发生了变化。循环条件可以写在代码块的后面,确保循环至少被执行一次。

​var​ ​n​ = ​2
​while​ ​n​ < ​100​ {
​    ​n​ = ​n​ * ​2
​}
​n
​
​var​ ​m​ = ​2
​do​ {
​    ​m​ = ​m​ * ​2
​} ​while​ ​m​ < ​100
​m

在循环中使用索引——或者用..<表示一个索引期间或者写一个明确的初始化、条件和自增语句。下面两个循环做了同样的事情:

​var​ ​firstForLoop​ = ​0
​for​ ​i​ ​in​ ​0​..<​4​ {
​    ​firstForLoop​ += ​i
​}
​firstForLoop
​
​var​ ​secondForLoop​ = ​0
​for​ ​var​ ​i​ = ​0​; ​i​ < ​4​; ++​i​ {
​    ​secondForLoop​ += ​i
​}
​secondForLoop

使用..<来表示删除上限的范围,使用…来表示完整的区间。

函数和闭包

使用func定义一个函数。函数名称后面跟一个元括号,其中写上参数值,这样就调用函数了。使用->分割参数名称和函数的返回值类型。

func​ ​greet​(​name​: ​String​,​day​: ​String​) -> ​String​ {
​    ​return​ ​"Hello ​\(​name​)​,today is ​\(​day​)​."
​}
​greet​(​"Bob"​,​"Tuesday"​)

实验
移除day参数。为问候语句添加一个代表今天午餐的参数。

使用元组表示一个组合值——比如,从一个函数返回多个值。元组中的元素可以既可以通过名称也可以通过索引被引用。

​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​([​5​,​100​,​9​])
​statistics​.​sum
​statistics​.​2

函数的参数个数允许是可变化的,函数体内会将其搜集起来放置在一个集合内。

func​ ​sumOf​(​numbers​: ​Int​...) -> ​Int​ {
​    ​var​ ​sum​ = ​0
​    ​for​ ​number​ ​in​ ​numbers​ {
​        ​sum​ += ​number
​    }
​    ​return​ ​sum
​}
​sumOf​()
​sumOf​(​42​,​597​,​12​)

实验

写一个计算参数平均值的函数。

函数是可以被嵌套的。嵌套在内部的函数可以访问外部函数定义的变量。可以用嵌套函数组织那些太长或者太复杂的函数。

func​ ​returnFifteen​() -> ​Int​ {
​    ​var​ ​y​ = ​10
​    ​func​ ​add​() {
​        ​y​ += ​5
​    }
​    ​add​()
​    ​return​ ​y
​}
​returnFifteen​()

函数是第一等类型。这意味着一个函数可以将另外一个函数作为返回值。

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​,​12​]
​hasAnyMatches​(​numbers​,​lessThanTen​)

函数实际上是一种特殊的闭包:代码块可以被稍后被调用。闭包中的代码可以访问在背包背创建的作用域内合法的诸如变量和函数,尽管在闭包被执行的时候的作用域已经发生了变化——你可以看看那些嵌套函数。你还可以用“{}”包裹代码但不给出名字的形式写一个闭包。在函数体内用in来分隔参数和返回值。

numbers​.​map​({
​    (​number​: ​Int​) -> ​Int​ ​in
​    ​let​ ​result​ = ​3​ * ​number
​    ​return​ ​result
​})

实验
重写那个闭包,对所有奇数都返回0。

对于闭包的写法,有几种更加简明的写法供选择。当闭包类型是已知的时候,例如闭包是一个委托的回调函数的情况,可以将其参数的类型、返回值类型的一个或全部省略掉。只有单独一条语句的闭包干脆就将唯一的语句作为返回值了。

let​ ​mappedNumbers​ = ​numbers​.​map​({ ​number​ ​in​ ​3​ * ​number​ })
​mappedNumbers

可以用数字取代名字引用参数——这个做法尤其适合简化闭包的写法。作为函数的最后一个参数的闭包可以立即出现在括号之后。

​let​ ​sortedNumbers​ = ​sorted​(​numbers​) { ​$0​ > ​$1​ }
​sortedNumbers

对象和类

使用class后面跟上类的名字来定义一个类。类中的属性声明和常量或者变量的声明是一样的,唯一不同的是需要在类的上下文环境中进行。同样的,方法和函数的声明的写法也是一样的。

​class​ ​Shape​ {
​    ​var​ ​numberOfSides​ = ​0
​    ​func​ ​simpleDescription​() -> ​String​ {
​        ​return​ ​"A shape with ​\(​numberOfSides​)​ sides."
​    }
​}

实验
用let添加一个常量属性,另外再添加一个带一个参数的方法。

在类名后写一对圆括号可以创建一个类的实例。使用点号来访问实例的属性或者方法。

​class​ ​NamedShape​ {
​    ​var​ ​numberOfSides​: ​Int​ = ​0
​    ​var​ ​name​: ​String
​    
​    ​init​(​name​: ​String​) {
​        ​self​.​name​ = ​name
​    }
​    
​    ​func​ ​simpleDescription​() -> ​String​ {
​        ​return​ ​"A shape with ​\(​numberOfSides​)​ sides."
​    }
​}

这样需要注意的是selft是如何被使用来区分name属性同构造方法中的name参数的。在创建一个类实例的时候,像调用函数一样给构造方法传递参数。类的每个属性都需要被分配一个值——不论是在声明阶段(如同numberOfSides一样)还是在构造方法中(如同name一样)。

如果有必要在对象被回收前做些清理工作,使用deinit 来创建一个析构函数。

子类包含了超类的名字,在子类的名字之后,用一个冒号分隔。类不必一定是一个标准根类的子类,所以你可以根据需要选择继承一个类或者不继承任意一个类。

子类的方法重写超类的实现用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​()

实验
创建NamedShape 类的另外一个子类Circle,它的构造方法有半径和名字作为参数。在Circle类中实现area和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"​)
​triangle​.​perimeter
​triangle​.​perimeter​ = ​9.9
​triangle​.​sideLength

在perimeter的setter中,新的值有一个默认的名字newValue。你也可以在set之后的圆括号内指定新值的名字。

注意EquilateralTriangle 类的构造方法分为了三步:

1.给子类中定义的属性赋值。
2.调用超类的构造方法。
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"​)
​triangleAndSquare​.​square​.​sideLength
​triangleAndSquare​.​triangle​.​sideLength
​triangleAndSquare​.​square​ = ​Square​(​sideLength​: ​50​,​name​: ​"larger square"​)
​triangleAndSquare​.​triangle​.​sideLength

类中的方法与函数有一个重要的区别。函数的参数名只在函数内使用,但是方法的参数名还可以在调用方法时(第一个参数除外)使用。默认情况下,一个方法在被调用时和在方法内部有相同的名字。你可以指定在方法内部使用的第二个名字。

class​ ​Counter​ {
​    ​var​ ​count​: ​Int​ = ​0
​    ​func​ ​incrementBy​(​amount​: ​Int​,​numberOfTimes​ ​times​: ​Int​) {
​        ​count​ += ​amount​ * ​times
​    }
​}
​var​ ​counter​ = ​Counter​()
​counter​.​incrementBy​(​2​,​numberOfTimes​: ​7​)

当处理可选类型值的时候,你可以在操作(像方法、属性和下标)之前写上?。如果?之前的值是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

实验
写一个函数通过比较两个Rank值的原始值,对其进行比较。

上面的例子中,枚举的原始值类型是Int,所以你只需要指定最开始的原始值。剩下的原始值会被按次序推测出来。当然你可以用字符串或者浮点数作为枚举的原始值。可以使用rawValue属性来访问枚举成员的原始值。

使用init?(rawValue:)构造方法来根据一个原始值创建一个枚举的实例。

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​()

实验
给Suit枚举添加一个color方法,对于spades和clubs返回“black”,对于heart和diamonds返回“red”。

这里注意上面两种对枚举中Hearts成员的引用:当给hearts常量赋值的时候,枚举成员Suit.Hearts被公告全名引用,因为那个常量还没有明确类型指定。在switch语句中,枚举成员被通过简写形式.Hearts引用,这是因为已经知道self的值是一个suit了。在值类型已知的前提下,你可以使用简写方式。

使用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​()

实验
给Card添加一个方法,创建全套的纸牌,每张牌都是排位和花色的组合。

一个枚举成员的实例可以有和实例的关联值。相同枚举成员的实例可以有不同的实例关联值。你可以在创建实例时为成员提供实例关联值。关联值和原始值是不同的:枚举成员的原始值对于所有它的实例都是相同的,当定义枚举的时候就可以提供原始值。

例如,向服务器索取日出和日落的时间。服务器会返回正确的内容,或者错误的信息。

enum​ ​ServerResponse​ {
​    ​case​ ​Result​(​String​,​String​)
​    ​case​ ​Error​(​String​)
​}
​
​let​ ​success​ = ​ServerResponse​.​Result​(​"6:00 am"​,​"8:09 pm"​)
​let​ ​failure​ = ​ServerResponse​.​Error​(​"Out of cheese."​)
​
​switch​ ​success​ {
​case​ ​let​ .​Result​(​sunrise​,​sunset​):
​    ​let​ ​serverResponse​ = ​"Sunrise is at ​\(​sunrise​)​ and sunset is at ​\(​sunset​)​."
​case​ ​let​ .​Error​(​error​):
​    ​let​ ​serverResponse​ = ​"Failure...  ​\(​error​)​"
​}

实验
给ServerResponse 添加一个case和对应的switch分支。

注意从ServerResponse 值中提取日出和日落时间的方式。(从作为switch分支上匹配值的一部分提取日出日落信息)

协议和扩展

使用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

实验
写一个遵循这个协议的枚举。

注意在SimpleStructure 定义时采用了mutating关键字标记一个修改结构体的方法。SimpleClass 类的声明就没有用mutating标记方法,因为类的方法都会修改类实例。

使用extension来给已经存在的类型添加功能,比如新的方法和计算属性。无论是在其他地方定义的类型还是导入的库还是框架中的类型,都可以用扩展添加对协议的支持。

extension​ ​Int​: ​ExampleProtocol​ {
​    ​var​ ​simpleDescription​: ​String​ {
​        ​return​ ​"The number ​\(​self​)​"
​    }
​    ​mutating​ ​func​ ​adjust​() {
​        ​self​ += ​42
​    }
​}
​7​.​simpleDescription

实验
写一个对于Double类型的扩展,添加一个absoluteValue 属性。

你可以向其他命名类型一样使用一个协议的名称——比如,创建一个包含不同类型但是都遵循同一协议的对象的集合。当处理这些类型是一个协议的值的时候,协议定义之外的方法是不可用的。

​let​ ​protocolValue​: ​ExampleProtocol​ = ​a
​protocolValue​.​simpleDescription
​// protocolValue.anotherProperty  // Uncomment to see the error

尽管变量protocolValue 有一个运行时类型SimpleClass,编译器还会将它按照ExampleProtocol类型处理。这就意味着你不能访问类中协议定义之外的方法和属性。

泛型

在一对尖括号内写一个名字就创建了一个泛型函数或者类型。

func​ ​repeat​<​Item​>(​item​: ​Item​,​times​: ​Int​) -> [​Item​] {
​    ​var​ ​result​ = [​Item​]()
​    ​for​ ​i​ ​in​ ​0​..<​times​ {
​        ​result​.​append​(​item​)
​    }
​    ​return​ ​result
​}
​repeat​(​"knock"​,​4​)

你可以创建泛型函数和泛型方法、类、枚举和结构体。

​// Reimplement the Swift standard library's optional type
​enum​ ​OptionalValue​<​T​> {
​    ​case​ ​None
​    ​case​ ​Some​(​T​)
​}
​var​ ​possibleInteger​: ​OptionalValue​<​Int​> = .​None
​possibleInteger​ = .​Some​(​100​)

在类型名称之后使用where来指定一个需求列表——例如,要求类型实现一个协议,要求两个类型是相同的,或者要求一个类有特定的超类。

​func​ ​anyCommonElements​ <​T​,​U​ ​where​ ​T​: ​SequenceType​,​U​: ​SequenceType​,​T​.​Generator​.​Element​: ​Equatable​,​T​.​Generator​.​Element​ == ​U​.​Generator​.​Element​> (​lhs​: ​T​,​rhs​: ​U​) -> ​Bool​ {
​    ​for​ ​lhsItem​ ​in​ ​lhs​ {
​        ​for​ ​rhsItem​ ​in​ ​rhs​ {
​            ​if​ ​lhsItem​ == ​rhsItem​ {
​                ​return​ ​true
​            }
​        }
​    }
​    ​return​ ​false
​}
​anyCommonElements​([​1​,​3​],[​3​])

实验
修改anyCommonElements 函数让它返回一个包含两个序列都有的元素的数组。

这个例子中,你可以省略where,在冒号之后写协议或者类的名字。 和是一个意思。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。