原文链接:http://www.objc.io/issue-16/swift-functions.html
前言
尽管OC跟其他语言有些很奇形怪状的语法,但是一旦你找到窍门方法语法还是相当直截了当的。快速回顾下:
+ (void)mySimpleMethod
{
// class method
// no parameters
// no return values
}
- (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2
{
// instance method
// one parameter of type NSString pointer, one parameter of type NSNumber pointer
// must return a value of type NSString pointer
return @"hello, world!";
}
对比下,Swift的语法看上去更像另外一种编程语言,可能比OC更复杂更容易混淆。
在我继续之前,我想澄清下Swift方法与函数的区别,因为我会通篇文章地使用它们。这里有个方法的定义,根据苹果官方教程:
Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type. Classes, structures, and enumerations can also define type methods, which are associated with the type itself. Type methods are similar to class methods in Objective-C.
函数是独立的,而方法是函数在类、结构体、枚举中的封装。
解剖Swift函数
举个Swift”Hello,World!”的栗子:
func mySimpleFunction() {
println("hello, world!")
}
如果你还用过除了OC之外的语言编程过,那么上面的函数会很熟悉哦。
-
func
关键字表明这是个函数 - 函数名是
mySimpleFunction
- 没有传参 – 空参
- 没有返回值
- 函数体执行域在{}内
现在我们整个稍微复杂点的函数:
func myFunctionName(param1: String, param2: Int) -> String {
return "hello, world!"
}
该函数带一个叫param1字符串型和另一个叫param2整型的参数,返回字符串。
调用所有的函数
Swift与OC之间最大得一个差别是在Swift函数参数怎么工作的。如果你跟我一样喜欢冗长的OC,当Swift函数被调用的时候默认参数名是没有一起跟进来搅和的。
func hello(name: String) {
println("hello \(name)")
}
hello("Mr. Roboto")
这看上去还不坏知道你为你的函数添加了其他的参数:
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")
如果碰到上面的情况,你要分清楚每个参数到底是什么就很头疼了。。
在Swift中,有一个概念是外部参数名来澄清混淆:
func hello(fromName name: String) {
println("\(name) says hello to you!")
}
hello(fromName: "Mr. Roboto")
上述函数中,fromName
是一个外部的参数,当函数被调用的时候被搅和进去了,但是name
内部参数用来引用函数执行时候内部的参数。
如果你想内外参数命名一致的话,你不用重复写参数名。。:
func hello(name name: String) {
println("hello \(name)")
}
hello(name: "Robot")
而是,只要捷径地添加一个#
放在参数名前面:
func hello(#name: String) {
println("hello \(name)")
}
hello(name: "Robot")
当然,这个规则跟方法中参数怎么工作还是有细微得区别的。。。
调用方法
当封装到一个类中,方法的第一个参数名不被外部包含,而接下来所有得参数名在方法调用的时候全部对外包含。
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")
因此这是你最佳实践来包含你的第一个参数名字到你的方法名(使用With),就像OC一样:
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来确定第一个参数是一个姓名。
如果因为某些特殊的原因让你想在函数中忽略外部参数名字(不建议),你可以使用_
作为外部参数名字:
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")
实例方法是柯里化函数
有件很酷的事情要了解下,那就是Swift中实例方法事实上是柯里化函数:
The basic idea behind currying is that a function can be partially applied, meaning that some of its parameter values can be specified (bound) before the function is called. Partial function application yields a new function.
那么假设我有一个类:
class MyHelloWorldClass {
func helloWithName(name: String) -> String {
return "hello, \(name)"
}
}
我可以创建一个变量指向helloWithName
这个函数:
let helloWithNameFunc = MyHelloWorldClass.helloWithName
// MyHelloWorldClass -> (String) -> String
我的新函数helloWithNameFunc
属于MyHelloWorldClass -> (String) -> String
类型,即一个函数带了我自定义类的一个实例作为参数并且返回另一个函数(这个函数带一个字符串参数并且返回一个字符串值)。
故实际上我可以这么调用我的函数:
let myHelloWorldClassInstance = MyHelloWorldClass()
helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto")
// hello, Mr. Roboto
Init:特别注意
在一个类,结构体,枚举初始化的时候特殊的init方法会被调用。在Swift中,你可以定义初始化参数,就像其他方法那样:
class Person {
init(name: String) {
// your init implementation
}
}
Person(name: "Mr. Roboto")
注意到不像其他方法,init方法在实例化的时候其第一个参数需要外部参数名字的。
这种时候最佳实践是添加一个可区分的外部名字-来增强初始化的可读性:
class Person {
init(fromName name: String) {
// your init implementation
}
}
Person(fromName: "Mr. Roboto")
当然,就跟其他方法一样,你可以添加一个_
如果你想让你的初始化方法忽略外部参数名字。举个官方栗子:
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 is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0
忽略外部参数在你如果想抽象化类/枚举/结构体的时候同样很有用。再举个某大师的栗子:
public struct JSValue : Equatable {
// ... truncated code
/// Initializes a new `JSValue` with a `JSArrayType` value.
public init(_ value: JSArrayType) {
self.value = JSBackingValue.JSArray(value)
}
/// Initializes a new `JSValue` with a `JSObjectType` value.
public init(_ value: JSObjectType) {
self.value = JSBackingValue.JSObject(value)
}
/// Initializes a new `JSValue` with a `JSStringType` value.
public init(_ value: JSStringType) {
self.value = JSBackingValue.JSString(value)
}
/// Initializes a new `JSValue` with a `JSNumberType` value.
public init(_ value: JSNumberType) {
self.value = JSBackingValue.JSNumber(value)
}
/// Initializes a new `JSValue` with a `JSBoolType` value.
public init(_ value: JSBoolType) {
self.value = JSBackingValue.JSBool(value)
}
/// Initializes a new `JSValue` with an `Error` value.
init(_ error: Error) {
self.value = JSBackingValue.Invalid(error)
}
/// Initializes a new `JSValue` with a `JSBackingValue` value.
init(_ value: JSBackingValue) {
self.value = value
}
}
不思议的参数
在Swift中,可选类型有一个新的概念定义:
选项参数类型
Optionals say either “there is a value, and it equals x” or “there isn’t a value at all.” Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes. Optionals are safer and more expressive than nil pointers in Objective-C and are at the heart of many of Swift’s most powerful features.
为了说明一个参数类型可能是可选的,只需要其后追加一个问号:
func myFuncWithOptionalType(parameter: String?) {
// function execution
}
myFuncWithOptionalType("someString")
myFuncWithOptionalType(nil)
当我们遇到选项类型时候,不要忘记了展开(做判断)!
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")
// The optional has a value! It's someString
myFuncWithOptionalType(nil)
// The optional is nil
当你做过OC开发,对比下选项类型毫无疑问节省了开发时间。
带默认值的参数
func hello(name: String = "you") {
println("hello, \(name)")
}
hello(name: "Mr. Roboto")
// hello, Mr. Roboto
hello()
// hello, you
注意到有默认值的参数自动有一个外部参数名字。
并且因为函数被调用的时候参数带默认值可以被忽略,最佳实践是把你带默认值的参数放在整个函数参数列表的末尾。根据官方文档智力需要注意一点:
Place parameters with default values at the end of a function’s parameter list. This ensures that all calls to the function use the same order for their non-default arguments, and makes it clear that the same function is being called in each case.
作者是带默认参数的粉丝,大部分是因为它增加代码可变性以及往后兼容性。你可以从特定的情形带两个参数开始,比如一个函数配置一个自定义的UITableViewCell
,并且如果另一个情形出现需要另外一个参数(比如label要不同的颜色)-而该函数其他所有得地方都是不需要改变的,那你代码中仅需要改变的是你只需要传第一个非默认的参数。
可变数量参数
可变数量参数相比传个数组而言是简单易读懂版本。实际上,如果你看完了上面外部参数名字的栗子,你将看出它是字符串数组类型:
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,就相当于传进去一个空数组,所以不要忘记做判断(作者说是需要时候,个人觉得还是都无脑判断下):
func helloWithNames(names: String...) {
if names.count > 0 {
for name in names {
println("Hello, \(name)")
}
} else {
println("Nobody here!")
}
}
helloWithNames()
// Nobody here!
可传入可传出参数(参数引用)
涉及到可传入可传出参数的时候你(参数引用),你可以对外部变量进行控制:
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
这是非常常见OC处理异常的场景,举个NSJSONSerialization
栗子:
- (void)parseJSONData:(NSData *)jsonData
{
NSError *error = nil;
id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!jsonResult) {
NSLog(@"ERROR: %@", error.description);
}
}
// 因为Swift还算新,没有关于错误处理的正式说明,但是这里有关于传入传出参数之外的选择!
泛型参数
直接举个栗子:
func valueSwap<T>(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
更多泛型章节请戳这里。
可修改的参数
默认情况下,带到函数里面的参数是常量,所以它们在函数域内是不能被操作的。如果你想改变这样的情况,只要在你的参数前加上var
关键字:
var name = "Mr. Roboto"
func appendNumbersToName(var name: String, #maxNumber: Int) -> String {
for i in 0..<maxNumber {
name += String(i + 1)
}
return name
}
appendNumbersToName(name, maxNumber:5)
// Mr. Robot12345
name
// Mr. Roboto
这里有必要与inout
做下对比,可修改参数并没有修改外部传进来的参数哦!
把函数作为参数
在Swift中,函数可以被当做参数传递。举个例子,一个函数可以把另外一个函数当做参数:
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
。这个函数被作为接收到的函数后续被执行。
实例方法同样也可以这么玩:
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
当然为了让你得函数定义更具有可读性,请考虑使用typealias
起别名。
typealias lotteryOutputHandler = (String, Int) -> String
func luckyNumberForName(name: String, #lotteryHandler: lotteryOutputHandler) -> String {
let luckyNumber = Int(arc4random() % 100)
return lotteryHandler(name, luckyNumber)
}
你也是用匿名函数作为函数参数(相当OC中的BLOCK)
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
在OC中,在遇到编写异步完成或者错误处理句柄时候很流行使用block做参数。同样在Swift中也能这么玩。
访问控制
Swift中有3种级别的访问控制:
- 公共访问(Public access)在该module中所有源文件都可以访问,其他module定义了这个module的话也可以使用。这种一般是框架级别的定义。
- 内部访问(Internal access)非该module的文件无法访问应用级别的定义或者框架内部结构的定义。
- 私有访问(Private access)只有定义源文件才能访问,使用这个级别可以隐藏不想公开的实现细节。
默认情况下,函数跟变量是内部的-如果你想做出改变,那么你不得不在方法和变量之前追加private or public
关键字:
public func myPublicFunc() {
}
func myInternalFunc() {
}
private func myPrivateFunc() {
}
private func myOtherPrivateFunc() {
}
来自Ruby的习惯,作者更喜欢吧他所有得私有方法写在类的底部,用注释隔开(作者吹牛逼)
class MyFunClass {
func myInternalFunc() {
}
// MARK: Private Helper Methods
private func myPrivateFunc() {
}
private func myOtherPrivateFunc() {
}
}
这里作者跟大苹果提出了建议。。。略一句
不思议的返回类型
Swift的返回值显然比OC复杂多了,特别是可选类型与多值类型。
可选类型返回值
你那有这样的情况吗:你的函数可以返回空值,你需要去确认返回值类型是否是可选的:
func myFuncWithOptonalReturnType() -> String? {
let someNumber = arc4random() % 100
if someNumber > 50 {
return "someString"
} else {
return nil
}
}
myFuncWithOptonalReturnType()
当然,当你使用可选类型返回值的时候,不要忘记了展开(做判断哦):
let optionalString = myFuncWithOptonalReturnType()
if let someString = optionalString {
println("The function returned a value: \(someString)")
} else {
println("The function returned nil")
}
作者看过最好关于可选类型的解释(是不是偏僻入里,意会不言传):
I finally get @SwiftLang optionals, they are like Schrödinger’s cat! You have to see if the cat is alive before you use it.
多值返回类型
Swift最让人鸡冻的特性是可以返回多值:
func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int) {
var min = numbers[0]
var max = numbers[0]
for number in numbers {
if number > max {
max = number
}
if number < min {
min = number
}
}
return (min, max)
}
findRangeFromNumbers(1, 234, 555, 345, 423)
// (1, 555)
正如你所见,上述返回了一组值。这里有两种方式来使用组值。
let range = findRangeFromNumbers(1, 234, 555, 345, 423)
println("From numbers: 1, 234, 555, 345, 423. The min is \(range.min). The max is \(range.max).")
// From numbers: 1, 234, 555, 345, 423. The min is 1. The max is 555.
let (min, max) = findRangeFromNumbers(236, 8, 38, 937, 328)
println("From numbers: 236, 8, 38, 937, 328. The min is \(min). The max is \(max)")
// From numbers: 236, 8, 38, 937, 328. The min is 8. The max is 937
多值返回混杂可选
当多值返回中返回值中夹杂这可选类型是需要技巧的,这里介绍两种处理方式。
在上述的栗子函数,逻辑是存在瑕疵的 – 有可能出现空值传入,那么我们程序可能会崩溃。如果没有值传入,那么我得将返回值设置成可选类型:
func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int)? {
if numbers.count > 0 {
var min = numbers[0]
var max = numbers[0]
for number in numbers {
if number > max {
max = number
}
if number < min {
min = number
}
}
return (min, max)
} else {
return nil
}
}
if let range = findRangeFromNumbers() {
println("Max: \(range.max). Min: \(range.min)")
} else {
println("No numbers!")
}
// No numbers!
其他情形下,可以个别设置返回值为可选类型而不是一棍子打成可选:
func componentsFromUrlString(urlString: String) -> (host: String?, path: String?) {
let url = NSURL(string: urlString)
return (url.host, url.path)
}
但是问题来了,如果你设置了你组返回值的某些返回值可选会导致在展开做判断的时候有点艰难,因为你必须去考虑每一种可选值的可能:
let urlComponents = componentsFromUrlString("http://name.com/12345;param?foo=1&baa=2#fragment")
switch (urlComponents.host, urlComponents.path) {
case let (.Some(host), .Some(path)):
println("This url consists of host \(host) and path \(path)")
case let (.Some(host), .None):
println("This url only has a host \(host)")
case let (.None, .Some(path)):
println("This url only has path \(path). Make sure to add a host!")
case let (.None, .None):
println("This is not a url!")
}
// This url consists of host name.com and path /12345
正如你所见,这不是你OC的处理模式。
函数作为返回值
Swift中一个函数可以返回值可以是另一个函数:
func myFuncThatReturnsAFunc() -> (Int) -> String {
return { number in
return "The lucky number is \(number)"
}
}
let returnedFunction = myFuncThatReturnsAFunc()
returnedFunction(5) // The lucky number is 5
咳咳,还是得用typealias
关键字增加可读性:
typealias returnedFunctionType = (Int) -> String
func myFuncThatReturnsAFunc() -> returnedFunctionType {
return { number in
return "The lucky number is \(number)"
}
}
let returnedFunction = myFuncThatReturnsAFunc()
returnedFunction(5) // The lucky number is 5
嵌套函数
如果上述的知识还没有喂饱你的话,这里还有一点就是在Swift中允许函数嵌套:
func myFunctionWithNumber(someNumber: Int) {
func increment(var someNumber: Int) -> Int {
return someNumber + 10
}
let incrementedNumber = increment(someNumber)
println("The incremented number is \(incrementedNumber)")
}
myFunctionWithNumber(5)
// The incremented number is 15
总结
Happy Swifting! 快乐编程!