原文链接 http://www.cocoachina.com/swift/20141002/9779.html
虽然 Objective-C 的语法相对于其他编程语言来说写法有点奇怪,但是当你真正使用的时候它的语法还是相当的简单。下面有一些例子:
1
2
3
4
5
6
7
8
9
10
11
|
+ (void)mySimpleMethod
{
// 类方法
// 无参数
// 无返回值}
- (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2
{
// 实例方法
// 其中一个参数是 NSString 指针类型,另一个是 NSNumber 指针类型
// 必须返回一个 NSString 指针类型的值
return
@
"hello, world!"
;
}
|
相比而言,虽然 Swift 的语法看起来与其他编程语言有更多相似的地方,但是它也可以比 Objective-C 更加复杂和令人费解。
在继续之前,我需要澄清 Swift 中方法和函数之间的不同,因为在本文中我们将使用这两个术语。按照 Apple 的 Swift Programming Language Book 里面的方法定义:
方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举 也可以定义类型方法,类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。
以上是长文慎读。一句话:函数是独立的,而方法是函数封装在类,结构或者枚举中的函数。
剖析 Swift 函数
让我们从简单的 “Hello,World” Swift 函数开始:
1
2
3
|
func mySimpleFunction() {
println(
"hello, world!"
)
}
|
如果你曾在 Objective-C 之外的语言进行过编程,上面的这个函数你会非常熟悉
func
表示这是一个函数。
函数的名称是 mySimpleFunction
。
这个函数没有参数传入 - 因此是( )
。
函数没有返回值
函数是在{ }
中执行
现在让我们看一个稍稍复杂的例子:
1
2
3
|
func myFunctionName(param1: String, param2: Int) -> String {
return
"hello, world!"
}
|
这个函数有一个 String
类型且名为 param1
的参数和一个 Int
类型名为 param2
的参数并且返回值是 `String 类型。
调用所有函数
Swift 和 Objective-C 之间其中一个巨大的差别就是当 Swift 函数被调用的时候参数工作方式。如果你像我一样喜欢 Objective-C 超长的命名方式,那么请记住,在默认情况下 Swift 函数被调用时参数名是不被外部调用包含在内的。
1
2
3
4
5
|
func hello(name: String) {
println(
"hello \(name)"
)
}
hello(
"Mr. Roboto"
)
|
在你增加更多参数到函数之前,一切看起来不是那么糟糕。但是:
1
2
3
4
5
|
func hello(name: String, age: Int, location: String) {
println(
"Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?"
)
}
hello(
"Mr. Roboto"
, 5,
"San Francisco"
)
|
如果仅阅读 hello("Mr. Roboto", 5, "San Francisco")
,你可能很难知道每一个参数代表什么。
在 Swift 中,有一个概念称为 *外部参数名称 * 用来解决这个困惑:
1
2
3
4
5
|
func hello(fromName name: String) {
println(
"\(name) says hello to you!"
)
}
hello(fromName:
"Mr. Roboto"
)
|
上面函数中,fromName
是一个外部参数,在函数被调用的时候将被包括在调用中。在函数内执行时,使用 name
这个内部参数来对输入进行引用。
如果你希望外部参数和内部参数相同,你不需要写两次参数名:
1
2
3
4
5
|
func hello(name name: String) {
println(
"hello \(name)"
)
}
hello(name:
"Robot"
)
|
只需要在参数前面添加 #
的快捷方式:
1
2
3
4
5
|
func hello(name name: String) {
println(
"hello \(name)"
)
}
hello(name:
"Robot"
)
|
当然,对于方法而言参数的工作方式略有不同...
调用方法
当被封装在类 (或者结构,枚举) 中时,方法的第一个参数名不被外部包含,同时所有的后面的参数在方法调用时候被外部包含:
1
2
3
4
5
6
7
8
9
10
|
class MyFunClass {
func hello(name: String, age: Int, location: String) {
println(
"Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?"
)
}
}
let myFunClass = MyFunClass()
myFunClass.hello(
"Mr. Roboto"
, age: 5, location:
"San Francisco"
)
|
因此最佳实践是在方法名里包含第一个参数名,就像 Objective-C 那样:
1
2
3
4
5
6
7
8
9
10
|
class MyFunClass {
func helloWithName(name: String, age: Int, location: String) {
println(
"Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?"
)
}
}
let myFunClass = MyFunClass()
myFunClass.helloWithName(
"Mr. Roboto"
, age: 5, location:
"San Francisco"
)
|
相对于调用函数 “hello”,我将其重命名为 helloWithName
,这使得第一个参数 name
变得很清晰。
如果出于一些原因你希望在函数中跳过外部参数名 (我建议如果要这么做的话,你需要一个非常好的理由),为外部函数添加 _
来解决:
1
2
3
4
5
6
7
8
9
10
|
class MyFunClass {
func helloWithName(name: String, _ age: Int, _ location: String) {
println(
"Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?"
)
}
}
let myFunClass = MyFunClass()
myFunClass.helloWithName(
"Mr. Roboto"
, 5,
"San Francisco"
)
|
实例方法是柯里化 (currying) 函数
需要注意一个非常酷的是 Swift 中实例方法是柯里化函数。
柯里化背后的基本想法是函数可以局部应用,意思是一些参数值可以在函数调用之前被指定或者绑定。这个部分函数的调用会返回一个新的函数。
如果我有一个类:
1
2
3
4
5
6
|
class MyHelloWorldClass {
func helloWithName(name: String) -> String {
return
"hello, \(name)"
}
}
|
我可以建立一个变量指向类中的 helloWithName
函数:
1
2
|
let helloWithNameFunc = MyHelloWorldClass.helloWithName
// MyHelloWorldClass -> (String) -> String
|
我新的 helloWithNameFunc
是 MyHelloWorldClass -> (String) -> String
类型,这个函数接受我的类的实例并返回另一个函数。新函数接受一个字符串值,并返回一个字符串值。
所以实际上我可以这样调用我的函数:
1
2
3
4
|
let myHelloWorldClassInstance = MyHelloWorldClass()
helloWithNameFunc(myHelloWorldClassInstance)(
"Mr. Roboto"
)
// hello, Mr. Roboto
|
初始化:一个特殊注意的地方
在类,结构体或者枚举初始化的时候将调用一个特殊的 init 方法。在 Swift 中你可以像其他方法那样定义初始化参数:
1
2
3
4
5
6
7
8
9
10
|
class Person {
init(name: String) {
// your init implementation
// 你的初始化方法实现
}
}
Person(name:
"Mr. Roboto"
)
|
注意下,不像其他方法,初始化方法的第一个参数必须在实例时必须是外部的。
大多数情况下的最佳实践是添加一个不同的外部参数名 — 本例中的 fromName
—让初始化更具有可读性:
1
2
3
4
5
6
7
8
9
10
|
class Person {
init(fromName name: String) {
// your init implementation
// 你的初始化方法实现
}
}
Person(fromName:
"Mr. Roboto"
)
|
当然,就像其他方法那样,如果你想让方法跳过外部参数名的话,可以添加 _
。我喜欢 Swift Programming Language Book 初始化例子的强大和可读性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
struct Celsius {
var
temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 是 37.0
|
如果你希望抽象类/枚举/结构体的初始化,跳过外部参数可以非常有用。我喜欢在 David Owen 的 json-swift library 中对这项技术的使用:
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
29
30
31
32
33
34
35
36
37
38
39
|
public struct JSValue : Equatable {
// ... 截断的部分代码
/// 使用 `JSArrayType` 来初始化 `JSValue`。
public init(_ value: JSArrayType) {
self.value = JSBackingValue.JSArray(value)
}
/// 使用 `JSObjectType` 来初始化 `JSValue`。
public init(_ value: JSObjectType) {
self.value = JSBackingValue.JSObject(value)
}
/// 使用 `JSStringType` 来初始化 `JSValue`。
public init(_ value: JSStringType) {
self.value = JSBackingValue.JSString(value)
}
/// 使用 `JSNumberType` 来初始化 `JSValue`。
public init(_ value: JSNumberType) {
self.value = JSBackingValue.JSNumber(value)
}
/// 使用 `JSBoolType` 来初始化 `JSValue`。
public init(_ value: JSBoolType) {
self.value = JSBackingValue.JSBool(value)
}
/// 使用 `Error` 来初始化 `JSValue`。
init(_ error: Error) {
self.value = JSBackingValue.Invalid(error)
}
/// 使用 `JSBackingValue` 来初始化 `JSValue`。
init(_ value: JSBackingValue) {
self.value = value
}
}
|
华丽的参数
相较于 Objective-C,Swift 有很多额外的选项用来指定可以传入的参数的类型,下面是一些例子。
可选参数类型
在 Swift 中有一个新的概念称之为 optional types:
可选表示 “那儿有一个值,并且它等于 x ” 或者 “那儿没有值”。可选有点像在 Objective-C 中使用 nil,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 nil 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
表明一个参数是可选 (可以是 nil),可以在类型规范后添加一个问号:
1
2
3
4
5
6
|
func myFuncWithOptionalType(parameter: String?) {
// function execution
}
myFuncWithOptionalType(
"someString"
)
myFuncWithOptionalType(nil)
|
使用可选时候不要忘记拆包!
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func myFuncWithOptionalType(optionalParameter: String?) {
if
let unwrappedOptional = optionalParameter {
println(
"The optional has a value! It's \(unwrappedOptional)"
)
}
else
{
println(
"The optional is nil!"
)
}
}
myFuncWithOptionalType(
"someString"
)
// optional has a value! It's someString
myFuncWithOptionalType(nil)
// The optional is nil
|
如果学习过 Objective-C,那么习惯使用可选值肯定需要一些时间!
参数默认值
1
2
3
4
5
6
7
8
9
|
func hello(name: String =
"you"
) {
println(
"hello, \(name)"
)
}
hello(name:
"Mr. Roboto"
)
// hello, Mr. Roboto
hello()
// hello, you
|
值得注意的是有默认值的参数自动包含一个外部参数名
由于参数的默认值可以在函数被调用时调过,所以最佳实践是把含有默认值的参数放在函数参数列表的最后。Swift Programming Language Book 包含相关的内容介绍:
把含有默认值的参数放在参数列表最后,可以确保对它的调用中所有无默认值的参数顺序一致,而且清晰表述了在不同情况下调用的函数是相同的。
我是默认参数的粉丝,主要是它使得代码容易改变而且向后兼容。比如配置一个自定义的 UITableViewCell
的函数里,你可以在你的某个用例中用两个参数开始,如果另一个用例出现,需要另一个参数 (比如你的 Cell 的 label 含有不同文字颜色),只需要添加一个包含新默认值的参数 — 函数的其他部分已经被正确调用,并且你代码最新部分仅需要参数传入一个非默认值。
可变参数
可变参数是传入数组元素的一个更加可读的版本。实际上,比如下面例子中的内部参数名类型,你可以看到它是 [String]
类型 (String 数组):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func helloWithNames(names: String...) {
for
name
in
names {
println(
"Hello, \(name)"
)
}
}
// 2 names
helloWithNames(
"Mr. Robot"
,
"Mr. Potato"
)
// Hello, Mr. Robot
// Hello, Mr. Potato
// 4 names
helloWithNames(
"Batman"
,
"Superman"
,
"Wonder Woman"
,
"Catwoman"
)
// Hello, Batman
// Hello, Superman
// Hello, Wonder Woman
// Hello, Catwoman
|
这里要特别记住的是可以传入 0 个值,就像传入一个空数组一样,所以如果有必要的话,不要忘记检查空数组:
1
2
3
4
5
6
7
8
9
10
11
12
|
func helloWithNames(names: String...) {
if
names.count > 0 {
for
name
in
names {
println(
"Hello, \(name)"
)
}
}
else
{
println(
"Nobody here!"
)
}
}
helloWithNames()
// Nobody here!
|
可变参数另一个要注意的地方是 — 可变参数必须是在函数列表的最后一个!
输入输出参数 inout
利用 inout 参数,你有能力 (经过引用来) 操纵外部变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var
name1 =
"Mr. Potato"
var
name2 =
"Mr. Roboto"
func nameSwap(inout name1: String, inout name2: String) {
let oldName1 = name1
name1 = name2
name2 = oldName1
}
nameSwap(&name1, &name2)
name1
// Mr. Roboto
name2
// Mr. Potato
|
这是 Objective-C 中非常常见的用来处理错误的模式。 NSJSONSerialization
是其中一个例子:
1
2
3
4
5
6
7
8
9
|
- (void)parseJSONData:(NSData *)jsonData
{
NSError *error = nil;
id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if
(!jsonResult) {
NSLog(@
"ERROR: %@"
, error.description);
}
}
|
Swift 非常之新,所以这里没有一个公认的处理错误的方式,但是在 inout 参数之外肯定有非常多的选择!看看 David Owen's 最新的博客 Swfit 中的错误处理。关于这个话题的更多内容已经在 Functional Programming in Swift 中被涵盖.
泛型参数类型
我不会在本文中大篇幅介绍泛型,但是这里有个简单的例子来阐述如何在一个函数中接受两个类型不定的参数,但确保这两个参数类型是相同的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func valueSwap(inout value1: T, inout value2: T) {
let oldValue1 = value1
value1 = value2
value2 = oldValue1
}
var
name1 =
"Mr. Potato"
var
name2 =
"Mr. Roboto"
valueSwap(&name1, &name2)
name1
// Mr. Roboto
name2
// Mr. Potato
var
number1 = 2
var
number2 = 5
valueSwap(&number1, &number2)
number1
// 5
number2
// 2
|
更多的泛型知识,我建议你阅读下 Swift Programming Language book 中的泛型章节。
变量参数 var
默认情况下,参数传入函数是一个常量,所以它们在函数范围内不能被操作。如果你想修改这个行为,只需要在你的参数前使用 var 关键字:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
var
name =
"Mr. Roboto"
func appendNumbersToName(
var
name: String,
#maxNumber: Int) -> String {
for
i
in
0..
let luckyNumber = Int(arc4random() % 100)
return
lotteryHandler(name, luckyNumber)
}
func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
return
"\(name), your lucky number is \(luckyNumber)"
}
luckyNumberForName(
"Mr. Roboto"
, lotteryHandler: defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38
let luckyNumber = Int(arc4random() % 100)
return
lotteryHandler(name, luckyNumber)
}
class FunLottery {
func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
return
"\(name), your lucky number is \(luckyNumber)"
}
}
let funLottery = FunLottery()
luckyNumberForName(
"Mr. Roboto"
, lotteryHandler: funLottery.defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38
func luckyNumberForName(name: String,
#lotteryHandler: lotteryOutputHandler) -> String {
let luckyNumber = Int(arc4random() % 100)
return
lotteryHandler(name, luckyNumber)
}
let luckyNumber = Int(arc4random() % 100)
return
lotteryHandler(name, luckyNumber)
}
luckyNumberForName(
"Mr. Roboto"
, lotteryHandler: {name, number
in
return
"\(name)'s' lucky number is \(number)"
})
// Mr. Roboto's lucky number is 74
}
func myInternalFunc() {
}
private func myPrivateFunc() {
}
private func myOtherPrivateFunc() {
}
func myInternalFunc() {
|