Swift中方法的多面性
虽然 Objective-C 的语法相对于其他编程语言来说写法有点奇怪,但是当你真正使用的时候它的语法还是相当的简单。下面有一些例子: + (void)mySimpleMethod { // 类方法 // 无参数 // 无返回值 } - (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2 { // 实例方法 // 其中一个参数是 NSString 指针类型,另一个是 NSNumber 指针类型 // 必须返回一个 NSString 指针类型的值 return @"hello,world!"; } 相比而言,虽然 Swift 的语法看起来与其他编程语言有更多相似的地方,但是它也可以比 Objective-C 更加复杂和令人费解。 在继续之前,我需要澄清 Swift 中方法和函数之间的不同,因为在本文中我们将使用这两个术语。按照 Apple 的Swift Programming Language Book里面的方法定义:
以上是长文慎读。一句话:函数是独立的,而方法是函数封装在类,结构或者枚举中的函数。 剖析 Swift 函数让我们从简单的 “Hello,World” Swift 函数开始: func mySimpleFunction() { println("hello,world!") } 如果你曾在 Objective-C 之外的语言进行过编程,上面的这个函数你会非常熟悉
现在让我们看一个稍稍复杂的例子: myFunctionName(param1: String,param2: Int) -> String { } 这个函数有一个 调用所有函数Swift 和 Objective-C 之间其中一个巨大的差别就是当 Swift 函数被调用的时候参数工作方式。如果你像我一样喜欢 Objective-C 超长的命名方式,那么请记住,在默认情况下 Swift 函数被调用时参数名是不被外部调用包含在内的。 hello(name: String) { "hello (name)") } hello("Mr. Roboto") 在你增加更多参数到函数之前,一切看起来不是那么糟糕。但是: (name: String,age: Int,location: String) { "Hello (name). I live in (location) too. When is your (age + 1)th birthday?") } hello("Mr. Roboto",5,"San Francisco") 如果仅阅读 在 Swift 中,有一个概念称为*外部参数名称* 用来解决这个困惑: (fromName name: String) { "(name) says hello to you!") } hello(fromName: 上面函数中, 只需要在参数前面添加 (#name: String) { 当然,对于方法而言参数的工作方式略有不同... 实例方法是柯里化 (currying) 函数需要注意一个非常酷的是Swift 中实例方法是柯里化函数。
如果我有一个类: MyHelloWorldClass { (name: String) -> String { (name)" } } 我可以建立一个变量指向类中的 let helloWithNameFunc = MyHelloWorldClass.helloWithName // MyHelloWorldClass -> (String) -> String 我新的 所以实际上我可以这样调用我的函数: let myHelloWorldClassInstance = MyHelloWorldClass() helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto") // hello,Mr. Roboto 初始化:一个特殊注意的地方 在类,结构体或者枚举初始化的时候将调用一个特殊的 Person { init(name: String) { // your init implementation // 你的初始化方法实现 } } Person(name: 注意下,不像其他方法,初始化方法的第一个参数必须在实例时必须是外部的。 如果你希望抽象类/枚举/结构体的初始化,跳过外部参数可以非常有用。我喜欢在David Owen的json-swift library中对这项技术的使用: public struct JSValue : Equatable { // ... 截断的部分代码 /// 使用 `JSArrayType` 来初始化 `JSValue`。 public init(_ value: JSArrayType) { self.value = JSBackingValue.JSArray(value) } /// 使用 `JSObjectType` 来初始化 `JSValue`。 (_ value: JSObjectType) { self.value = JSBackingValue.JSObject(value) } /// 使用 `JSStringType` 来初始化 `JSValue`。 (_ value: JSStringType) { self.value = JSBackingValue.JSString(value) } /// 使用 `JSNumberType` 来初始化 `JSValue`。 (_ value: JSNumberType) { self.value = JSBackingValue.JSNumber(value) } /// 使用 `JSBoolType` 来初始化 `JSValue`。 (_ 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:
表明一个参数是可选 (可以是 nil),可以在类型规范后添加一个问号: myFuncWithOptionalType(parameter: String?) { // function execution } myFuncWithOptionalType("someString") myFuncWithOptionalType(nil) 使用可选时候不要忘记拆包! (optionalParameter: String?) { if let unwrappedOptional = optionalParameter { "The optional has a value! It's (unwrappedOptional)") } else { "The optional is nil!") } } myFuncWithOptionalType("someString") // optional has a value! It's someString myFuncWithOptionalType(nil) // The optional is nil 如果学习过 Objective-C,那么习惯使用可选值肯定需要一些时间! 参数默认值(name: String = "you") { (name)") } hello(name: "Mr. Roboto") 值得注意的是有默认值的参数自动包含一个外部参数名 由于参数的默认值可以在函数被调用时调过,所以最佳实践是把含有默认值的参数放在函数参数列表的最后。Swift Programming Language Book包含相关的内容介绍:
我是默认参数的粉丝,主要是它使得代码容易改变而且向后兼容。比如配置一个自定义的 可变参数 可变参数是传入数组元素的一个更加可读的版本。实际上,比如下面例子中的内部参数名类型,你可以看到它是 helloWithNames(names: String...) { for name in names { "Hello,(name)") } } // 2 names helloWithNames("Mr. Robot",0)">"Mr. Potato") // Hello,Mr. Robot // 4 names helloWithNames("Batman",0)">"Superman",0)">"Wonder Woman",0)">"Catwoman") 这里要特别记住的是可以传入 0 个值,就像传入一个空数组一样,所以如果有必要的话,不要忘记检查空数组: if names.count > 0 { in names { (name)") } } "Nobody here!") } } helloWithNames() // Nobody here! 可变参数另一个要注意的地方是 — 可变参数必须是在函数列表的最后一个! 输入输出参数 inout利用 inout 参数,你有能力 (经过引用来) 操纵外部变量: var name1 = "Mr. Potato" var name2 = "Mr. Roboto" 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 中非常常见的用来处理错误的模式。 - (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中被涵盖. 泛型参数类型我不会在本文中大篇幅介绍泛型,但是这里有个简单的例子来阐述如何在一个函数中接受两个类型不定的参数,但确保这两个参数类型是相同的: valueSwap<T>inout value1: T,168)">inout value2: T) { let oldValue1 = value1 value1 = value2 value2 = oldValue1 } "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 关键字: var name = appendNumbersToNamevar 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 中,函数可以被用来当做变量传递。比如,一个函数可以含有一个函数类型的参数: luckyNumberForName(String,Int) -> String) -> let luckyNumber = Int(arc4random() % 100) return lotteryHandler(name,luckyNumber) } defaultLotteryHandler"(name),your lucky number is (luckyNumber)" } luckyNumberForName(// Mr. Roboto,your lucky number is 38 注意下只有函数的引用被传入 — 在本例中是 实例方法也可以用类似的方法传入: FunLottery { (luckyNumber)" } } let funLottery = FunLottery() luckyNumberForName( 为了让你的函数定义更具可读性,可以考虑为你函数的类型创建别名 (类似于 Objective-C 中的 typedef):typealiaslotteryOutputHandler = (String,Int) -> String 在 Objective-C 中,使用 blocks 作为参数是异步操作是操作结束时的回调和错误处理的常见方式,这一方式在 Swift 中得到了很好的延续。 权限控制Swift 有三个级别的权限控制:
默认情况下,每个函数和变量是 internal 的 —— 如果你希望修改他们,你需要在每个方法和变量的前面使用 public func myPublicFunc() { } func myInternalFuncprivate func myPrivateFuncmyOtherPrivateFunc() { } Ruby 带来的习惯,我喜欢把所有的私有函数放在类的最下面,利用一个 MyFunClass { () { } // MARK: Private Helper Methods () { } () { } } 希望 Swift 在将来的版本中包含一个选项可以用一个私有关键字来表明以下所有的方法都是私有的,类似于其他语言那样做访问控制。 华丽的返回类型在 Swift 中,函数的返回类型和返回值相较于 Objective-C 而言更加复杂,尤其是引入可选和多个返回类型。 可选返回类型如果你的函数有可能返回一个 nil 值,你需要指定返回类型为可选: myFuncWithOptonalReturnType() -> String? { let someNumber = arc4random() % 100 if someNumber > 50 { "someString" } else { return nil } } myFuncWithOptonalReturnType() 当然,当你使用可选返回值,不要忘记拆包: let optionalString = myFuncWithOptonalReturnType() let someString = optionalString { "The function returned a value: (someString)") } else { "The function returned nil") } The best explanation I've seen of optionals is from atweet by @Kronusdark:
多返回值Swift 其中一个令人兴奋的功能是函数可以有多个返回值 findRangeFromNumbers(numbers: Int...) -> (min: Int,max: Int) { var min = numbers[0] max = numbers[0] for number in numbers { if number > max { max = number } if number < min { min = number } } return (min,31)">max) } findRangeFromNumbers(1,31)">234,31)">555,31)">345,31)">423) // (1,555) 就像你看到的那样,在一个元组中返回多个值,这一个非常简单的将值进行组合的数据结构。有两种方法可以使用多返回值的元组: let range = findRangeFromNumbers(423) "From numbers: 1,234,555,345,423. The min is (range.min). The max is (range.max).") // From numbers: 1,423. The min is 1. The max is 555. let (max) = findRangeFromNumbers(236,31)">8,31)">38,31)">937,31)">328) "From numbers: 236,8,937,328. The min is (min). The max is (max)") // From numbers: 236,328. The min is 8. The max is 937 多返回值与可选值当返回值是可选的时候,多返回值就比较棘手。对于多个可选值的返回,有两种办法解决这种情况。 在上面的例子函数中,我的逻辑是有缺陷的 —— 它有可能没有值传入,所以我的代码有可能在没有值传入的时候崩溃,所以我希望我整个返回值是可选的: max: Int)? { if numbers.0 { 0] 0] in numbers { max { max = number } min { min = number } } max) } nil } } let range = findRangeFromNumbers() { "Max: (range.max). Min: (range.min)") } "No numbers!") } // No numbers! 另一种做法是对元组中的每个返回值设为可选来代替整体的元组可选: 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)") "This url only has a host (host)") (.None,.Some(path)): "This url only has path (path). Make sure to add a host!") "This is not a url!") } // This url consists of host name.com and path /12345 如你所见,它和 Objective-C 的处理方式完全不同! 返回一个函数Swift 中函数可以返回一个函数: myFuncThatReturnsAFunc() -> (Int) -> return { number in "The lucky number is (number)" } } let returnedFunction = myFuncThatReturnsAFunc() returnedFunction(5) // The lucky number is 5 为了更具有可读性,你当然可以为你的返回函数定义一个别名: typealias returnedFunctionType = (Int) -> String returnedFunctionType { // The lucky number is 5 嵌套函数如果在这篇文章中你没对函数有足够的体会,那么了解下 Swift 可以在函数中定义函数也是不错的。 myFunctionWithNumber(someNumber: Int) { incrementvar someNumber: Int) -> Int { return someNumber + 10 } let incrementedNumber = increment(someNumber) "The incremeted number is (incrementedNumber)") } myFunctionWithNumber(// The incremeted number is 15 @endSwift 函数有更多的选项以及更为强大功能。从你开始利用 Swift 编程时,记住:能力越强责任越大。请一定要巧妙地优化可读性! Swift 的最佳实践还没被确立,这门语言也在不断地进化,所以请朋友和同事来审查你的代码。我发现一些从来没见过 Swift 的人反而在我的 Swift 代码中提供了很大帮助。 Swift 编码快乐! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |