转载 Swift方法的多面性

原文链接  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  in  0.. " String(i + 1)"      }= ""      return  name= "" }= "" appendnumberstoname(name, maxnumber:5)= ""   mr. robot12345= "" name= ""   mr. roboto<= "" pre= "" >

 值得注意的是这个和 inout 参数不同 — 变量参数不会修改外部传入变量!

作为参数的函数

在 Swift 中,函数可以被用来当做变量传递。比如,一个函数可以含有一个函数类型的参数:

 
          "brush:js;toolbar:false" 
          >func luckyNumberForName(name: String,  
          #lotteryHandler: (String, Int) -> String) -> String { 
         
     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

注意下只有函数的引用被传入 — 在本例中是 defaultLotteryHandler。这个函数之后是否执行是由接收的函数决定。

实例方法也可以用类似的方法传入:

 
          "brush:js;toolbar:false" 
          >func luckyNumberForName(name: String,  
          #lotteryHandler: (String, Int) -> String) -> String { 
         
     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

为了让你的函数定义更具可读性,可以考虑为你函数的类型创建别名 (类似于 Objective-C 中的 typedef):

 
          "brush:js;toolbar:false" 
          >typealias lotteryOutputHandler = (String, Int) -> String 
         
 
func luckyNumberForName(name: String,  #lotteryHandler: lotteryOutputHandler) -> String {
     let luckyNumber = Int(arc4random() % 100)
     return  lotteryHandler(name, luckyNumber)
}

你也可以使用不包含参数名的函数 (类似于 Objective-C 中的 blocks):

 
          "brush:js;toolbar:false" 
          >func luckyNumberForName(name: String,  
          #lotteryHandler: (String, Int) -> String) -> String { 
         
     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

在 Objective-C 中,使用 blocks 作为参数是异步操作是操作结束时的回调和错误处理的常见方式,这一方式在 Swift 中得到了很好的延续。

权限控制

    " list-paddingleft-2" >
    
  •     

    Public 权限 可以为实体启用定义它们的模块中的源文件的访问,另外其他模块的源文件里只要导入了定义模块后,也能进行访问。通常情况下,Framework 是可以被任何人使用的,你可以将其设置为 public 级别

        
        
  •     

    Internal 权限 可以为实体启用定义它们的模块中的源文件的访问,但是在定义模块之外的任何源文件中都不能访问它。通常情况下,app 或 Framework 的内部结构使用 internal 级别。

        
        
  •     

    Private 权限 只能在当前源文件中使用的实体。使用 private 级别,可以隐藏某些功能的特地的实现细节。

        

    默认情况下,每个函数和变量是 internal 的 —— 如果你希望修改他们,你需要在每个方法和变量的前面使用 private 或者 public 关键字:

     
              "brush:js;toolbar:false" 
              >public func myPublicFunc() { 
             
     
    }
     
    func myInternalFunc() {
     
    }
     
    private func myPrivateFunc() {
     
    }
     
    private func myOtherPrivateFunc() {
     
    }

    Ruby 带来的习惯,我喜欢把所有的私有函数放在类的最下面,利用一个 //MARK 来区分:

     
              "brush:js;toolbar:false" 
              >class MyFunClass { 
             
     
         func myInternalFunc() {
     

    你可能感兴趣的:(转载 Swift方法的多面性)