原文连接:http://www.objc.io/issue-16/swift-functions.htmlhtml
尽管OC跟其余语言有些很奇形怪状的语法,可是一旦你找到窍门方法语法仍是至关直截了当的。快速回顾下:ios
+ (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更复杂更容易混淆。express
在我继续以前,我想澄清下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.json
函数是独立的,而方法是函数在类、结构体、枚举中的封装。swift
举个Swift"Hello,World!"的栗子:数组
func mySimpleFunction() { println("hello, world!") }
若是你还用过除了OC以外的语言编程过,那么上面的函数会很熟悉哦。app
func
关键字代表这是个函数mySimpleFunction
如今咱们整个稍微复杂点的函数:框架
func myFunctionName(param1: String, param2: Int) -> String { return "hello, world!" }
该函数带一个叫param1字符串型和另外一个叫param2整型的参数,返回字符串。dom
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方法会被调用。在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种级别的访问控制:
默认状况下,函数跟变量是内部的-若是你想作出改变,那么你不得不在方法和变量以前追加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! 快乐编程!