您的位置:首页 > 移动开发 > Swift

Swift语言 快速基础入门 (2)

2015-10-18 09:15 579 查看
(文章出处:http://blog-cn.jiazhewang.com/swift%E8%AF%AD%E8%A8%80-%E5%BF%AB%E9%80%9F%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8-2/

本文根据《The Swift Programming Language》一书 “A Swift Tour” 章节进行了简单地翻译,并加入了一些我个人的实验记录和想法,以求用比较快的速度熟悉 Swift 语言的语法和特性。

本文内容分为两部分,本页是第(2)部分,第一部分请点此浏览:Swift语言
快速基础入门 (1)


对象和类 Objects and Classes


使用 class 关键字来声明一个类,关键字之后跟着的是类名。类的属性声明和常量以及变量的声明方式完全想通,唯一不同的是他们被声明在类的大括号内。同样的,方法和函数的声明也是如此。

123456class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with (numberOfSides) sides." }}
要创建一个类的实例,我们可以在类名后加上括号,比如下面例子中那样。我们可以使用“点语法”(dot syntax)来直接访问实例的属性和方法。

1

2

3

varshape=Shape()

shape.numberOfSides=7

varshapeDescription=shape.simpleDescription()

这个版本的 Shape 方法很不靠谱,因为缺少了一个很重要的东西:一个用来设置初始值的方法。我们通常称之为构造函数(constructor、initializer)。在 Swift 中,我们默认通过类中命名为 init 的方法来作为这个类的构造函数。

123456789101112class 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 这个参数的。第6行中,后面的name 是 init 这个构造函数的参数,而这个参数被传给了 self.name 这个类的属性。当你创造一个实例的时候,像函数调用参数一样直接给构造器直接传入参数。每一个类的属性都要被赋值,不管是通过直接声明的方式(就像 numberOfsides)还是通过构造函数(就像name)。如果你还需要在删除对象之前进行一些清理工作,可以使用命名为 deinit 的类内方法来创建一个析构函数(deinitializer)。

继承——子类和父类

声明子类的方式是在其类名后面写一个冒号,后面跟上父类的名字。由于没有必要继承任何标准的根类,所以我们可以根据实际需要来选择继承或者省略父类。子类中的方法如果重写了父类的方法,在声明前加关键字 override 来标明。如果没有标明的话,会被编译器报错。编译器同样会检测标明了重写但是实际根本没有重写的方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class
Square:
NamedShape{

var
sideLength:
Double

init(sideLength:
Double,
name:
String){

self.sideLength=sideLength

super.init(name:
name)

numberOfSides=4

}

funcarea()-> Double{

returnsideLength*sideLength

}

overridefuncsimpleDescription()->String{

return"A
square with sides of length (sideLength)."

}

}

lettest=Square(sideLength:5.2,name:"my
test square")

test.area()

test.simpleDescription()


Getter 和 Setter

对于一些简单的属性,可以设置 getter 和 setter。比如下面例子中的 perimeter。

1234567891011121314151617181920212223242526class 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 triagle with sides of length (sideLength)." } } var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle") triangle.perimeter triangle.perimeter = 9.9 triangle.sideLength

方法 vs 函数

Swift 中,“类中的方法”和一般“函数”有一个重要的区别,那就是参数名。函数的参数名只在函数内部被使用,但是方法的参数名在调用方法时需要写出来。(但是为了对于很多只有一个参数的方法来说简便一点,第一个参数名默认不需要写出)默认地,调用时的参数名与方法内的参数名一样,但是也可以在方法内自定义第二个参数名,具体看下面的例子。

1

2

3

4

5

6

7

8

classCounter{

var
count:
Int=0

funcincrementBy(amount:
Int,numberOfTimes
times:
Int){

count+=amount*times

}

}

varcounter=Counter()

counter.incrementBy(2,numberOfTimes:7)

这个例子中,incrementBy 是一个方法,它有两个参数。第一个参数叫做 amount,在调用的时候,由于是第一个参数,所以直接传入2。第二个参数对外叫做 numberOfTimes,在调用的时候需要写 numberOfTimes:7来传入7,而对内的时候有一个自定义的名字叫做 times,所以方法内部代码直接使用了 amount*times。这样的好处是,写方法的时候使用较精简的自定义的参数名可以简化编程过程,而调用方法的时候使用较为详细的参数名可以方便理解参数的意义。


可选值

当使用可选值时,你可以在方法、属性或者子脚本之前加上一个问号?。这样的话,如果问号前的值是 nil,那么问号之后的所有内容都会被忽略,然后整个表达式返回的是 nil。否则,如果问号前的值存在,那么问号后的代码会被运行。这两种情况下,真个表达式的值也都是一个可选值。

12let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")let sideLength = optionalSquare?.sideLength

枚举和结构 Enumerations and Structures

枚举

使用关键字 <span #d333f5;"="" style="box-sizing: border-box; border: 0px; font-family: inherit; font-style: inherit; font-weight: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;">enum 来创建一个枚举。如同类和其他所有被命名的类型一样,枚举可以拥有自己的方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

enum
Rank:
Int{

caseAce=1

caseTwo,Three,Four,Five,Six,Seven,Eight,Nine,Ten

caseJack,Queen,King

funcsimpleDescription()->String{

switchself{

case.Ace:

return"ace"

case.Jack:

return"jack"

case.Queen:

return"queen"

case.King:

return"king"

default:

returnString(self.toRaw())

}

}

}

letace=Rank.Ace//
(Enum Value)

letaceRawValue=ace.toRaw()//
1

letaceDescribe=ace.simpleDescription()//
"ace"

在上面的例子中,这个枚举的原始值的类型(raw value type)是整型 Int。所以你只需要设置第一个原始值1,然后剩下的原始值会被自动按照顺序赋值。当然,你也可以使用字符串(String)或者浮点数(Floating-point numbers)作为枚举的原始值。

使用 toRaw 和 fromRaw 函数来实现原始值和枚举值之间的转换。

123if let convertedRank = Rank.fromRaw(3) { let threeDescription = convertedRank.simpleDescription()}
比如上面例子中,Rank.Ace 就是枚举值,它对应的原始值是 1.
Rank.fromRaw(1) 就是 Rank.Ace
Rank.Ace.toRaw() 就是 1枚举的成员值才是重要的,是实际值,并不只是原始值的另一种表示方式。事实上,如果原始值没有什么太大的意义的话我们甚至可以不设置原始值。比如下面的例子就没有设置原始值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

enumSuit{

caseSpades,Hearts,Diamonds,Clubs

funcsimpleDescription()->String{

switchself{

case.Spades:

return"spades"

case.Hearts:

return"hearts"

case.Diamonds:

return"diamonds"

case.Clubs:

return"clubs"

}

}

}

lethearts=Suit.Hearts

letheartsDescription=hearts.simpleDescription()

注意,在给常量 hearts 赋值的时候,我们用的是“枚举名.成员值”的格式,比如 Suit.Hearts。但是在枚举内部 switch self 中我们使用了缩写 .Hearts, 这是因为 self 已经知道是一个自身枚举类型 suit 了,所以可以使用缩写。


结构体 structure

使用关键字 struct 来创建一个结构体。 结构体和类有很多相似之处,包括拥有方法和构造函数。他们的一个最最重要的区别是,结构体是传值,被传递的时候永远会被拷贝。但是类被传递是被传递引用。

123456789struct 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 作为一个包含 Rank 和 Suit 成员的结构体,非常生动。在创建实例的时候,直接使用Card(rank: .Three, suit: .Spades) 非常清晰。一个枚举成员的实例可以有对应的实例值(见下面例子中的 ServerResponse.Result(“6:00 am”, “8:09 pm”),时间可以设置成不同,那么就是不同实例值的实例)。相同枚举成员的实例可以有不同的值。当实例被创建的时候,对应的值应该被赋值。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且原始值是在定义枚举的时候就被定下的。让我们看一个例子,当要从服务器获取日出和日落的时间的时候,服务器要么返回对应的信息要么返回报错信息。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

enumServerResponse{

caseResult(String,String)

caseError(String)

}

letsuccess=ServerResponse.Result("6:00
am","8:09
pm")

letfailure=ServerResponse.Error("Out
of cheese.")

switchsuccess{

caselet.Result(sunrise,sunset):

letserverResponse="Sunrise
is at (sunrise) and sunset is at (sunset)."

caselet.Error(error):

letserverResponse="Failure... (error)"

}

注意 sunrise 和 sunset 时间是怎么作为 switch 中的匹配部分而从 ServerResponse 值中提取出来的。


协议(接口)和扩展 Protocols and Extensions


协议(接口)

首先解释一下,苹果从Objective-C 开始,传统意义上的接口,也就是 C#,java,Pascal 等语言中的接口(Interface)就被称为“协议”(protocol)。协议(@protocol)本身不实现任何方法,只声明方法,然后使用此协议的类必须实现协议中的方法。你可以看出,这就是其他语言中“接口”的概念。而 Obj-C 中的“接口”(@interface)只是一个类的声明,声明使用了对应的类。所以我们在学习 Swift 时,看到协议(protocol)的概念,就可以把它当做接口来理解。

使用关键字 protocol 来声明一个协议。

1234protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust()}
这个协议规定了一个字符串变量 simpleDescription 以及一个变异性方法 adjust。类,枚举,构造体都可以应用协议。任何应用此协议的类,枚举,构造体都拥有这个变量属性,都要实现这个方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

protocolExampleProtocol{

var
simpleDescription:
String{get}

mutatingfuncadjust()

}

class
SimpleClass:
ExampleProtocol{

var
simpleDescription:
String="A
very simple class."

var
anotherProperty:
Int=69105

funcadjust(){

simpleDescription+=" Now
100% adjusted."

}

}

vara=SimpleClass()

a.adjust()

letaDescription=a.simpleDescription

struct
SimpleStructure:
ExampleProtocol{

var
simpleDescription:
String="A
simple structure"

mutatingfuncadjust(){

simpleDescription+="
(adjusted)"

}

}

varb=SimpleStructure()

b.adjust()

letbDescription=b.simpleDescription

注意声明结构体的时候用到了 mutating 这个关键字,它是用来标记一个会修改结构体的方法的。我们知道,结构体不是使用引用传递的,这里和类有些区别。类中不需要使用这个关键字,因为类中所有的方法都是会修改类的。


扩展 extension

使用关键字 extension 来为现有的类型增加一些附加功能,比如新方法和计算过的属性。你可以使用扩展来给任意在别处声明的类型添加一致性的协议,或者甚至直接添加到从外部库或者框架中导入的类型。

123456789extension Int: ExampleProtocol { var simpleDescription: String { return "The number (self)" } mutating func adjust() { self += 42 }}7.simpleDescription
上面这个拓展直接拓展了整型Int,给所有整型数据增加了一些方法和属性。

1

2

3

letprotocolValue:
ExampleProtocol=a

protocolValue.simpleDescription

//
protocolValue.anotherProperty // Uncomment to see the error

我们可以直接使用协议名(比如例子中的 ExampleProtocol)来作为一种类型。它表示了所有应用了这个协议的类型的集合。比如例子中的 ExampleProtocol 就表示了 SimpleClass 类和 SimpleStructure 结构体的集合的类型。这个类型拥有所有协议规定的属性和方法,但是不具有每个类型自身衍生的属性和方法,比如 SimpleClass 中,有协议没有规定的额外属性 anotherProperty。这个属性 ExampleProtocol 类型的实例就没有,不能访问。即使 protocolValue
在运行时的类型是 SimpleClass,编译器也会把它当做 ExampleProtocol 类型。所以不可以调用类在协议以外声明的属性或者方法了。


泛型 Generics

在尖括号 <> 里写一个名字来创建一个泛型函数或者是类型。通过泛型我们可以创建不需要规定应用类型的对象。

12345678func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] { var result = ItemType[]() for i in 0..times { result += item } return result}repeat("knock", 4)
你也可以创建泛型类,泛型枚举和泛型结构体。比如下面的例子是 Swift 中对可选值实现的对应的泛型枚举。

1

2

3

4

5

6

7

//
Reimplement the Swift standard library's optional type

enumOptionalValue<T>{

caseNone

caseSome(T)

}

varpossibleInteger:
OptionalValue<Int>=.None

possibleInteger=.Some(100)


泛型限定

在类型名后使用关键字 where 来指定一个需求列表:比如,要求此类型应用一个协议,要求两个类型相同,或者要求一个继承特定的父类。

1

2

3

4

5

6

7

8

9

10

11

funcanyCommonElements<T,Uwhere
T:
Sequence,
U:
Sequence,T.GeneratorType.Element:
Equatable,T.GeneratorType.Element==U.GeneratorType.Element>(lhs:
T,
rhs:
U)->Bool{

forlhsIteminlhs{

forrhsIteminrhs{

iflhsItem==rhsItem{

returntrue

}

}

}

returnfalse

}

anyCommonElements([1,2,3],[3])

比如上面的例子中声明了一个泛型函数,需要使用两个参数,分别是泛型 T 和泛型 U。并对它们分别做出了限定。

简单一点的话,也可以直接省略 where 关键字,只在冒号后面写上协议名或者类名来限定此类型应用该协议和次类型继承该父类。比如 <T: Equatable> 等价于 <T where T: Equatable> 。

点此浏览:Swift语言
快速基础入门 (1)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: