函数
swift的函数语法算是比较独特的了, 而且它还很重要哦, 因为最终你所有的代码都是要归结到某个函数里,也就是所谓的 action
.
函数参数和返回值
func sum (_ x:Int, _ y:Int) -> Int {
let result = x + y
return result
}
看上去很不错,而且没有语法错误,但是你并没有真正懂得它到底发生了什么. 下面我们来断行解析一下:
func sum //①
(_ x:Int, _ y:Int) //②③
-> Int { //④⑤
let result = x + y //⑥
return result // ⑦
}
没有返回值和参数
函数可以没有返回值和参数. 但是调用函数的时候会别忘记了()
.
其实函数没有返回值并不是说它什么都没有返回,而是返回额一个 Void
的类型的值.
函数签名
定义函数的时候一时想不起好名字完全可以省略,而是只有它实际的类型:
(Int, Int) -> Int
具有相同的参数类型以及返回值,他们就是函数类型
.
**一个合法的函数签名必须具有参数和返回值部分,如果它没有参数和返回值,那简写为
- (Void) -> Void
- () -> ()
外部参数名
一个函数可以给它的参数指定外在的名字, 这样在调用这个函数的时候,就需要给它的参数带上这个外在的名字. 这样做有几个好处就是:
- 它能够说明这个参数的目的. 通过这个参数的标签就能够简单说明它是什么动作;
- 区别于其他的函数
- 更好的和 oc 或者 cocoa 协作.
对于 swift 来说默认的所有参数都是自动被参数外部化
. 所以当如果内部名和外部名一样的话那就说明都不需要做,而如果和默认的行为不一样就需要:
-
修改外部名
如果想要修改外部名, 那就在外部名和内部名之间加一个空格.
-
抑制外部名的产生
这个情况直接通过使用
下划线空格
的形式.
func funcwithexternal(shit s:String) -> String {
return s
}
let s = funcwithexternal(shit: "hello, world");
print(s)
func funcwithsupresexternal(_ s:String, shit ss:String) -> String {
return s+ss
}
let ss = funcwithsupresexternal("hello", shit:"world")
print(ss)
参数名
重载
swift 支持函数的重载, 也就是说可以具有相同的签名和返回类型:
func say (_ what:String) {
}
func say (_ what:Int) {
}
对于返回值,需要明确的它的类型.
如果我们同时定义了:
func say() -> String {
return "one"
}
func say() -> Int {
return 1
}
而在调用的时候直接这样 let result = say()
编译器就汇报哦错.
而要是这样:
func giveMeAString(_ s:String) {
print("thanks!")
}
我们把giveMeAString(say())
作为参数传入 giveMeString
就会没事.
默认参数值
class Dog {
func say(_ s:String, times:Int = 1) {
for _ in 1...times {
print(s)
}
}
}
我们可以这样调用它:
let d = Dog()
d.say("woof")
可变参数
func sayStrings(_ arrayOfStrings:String ...) {
for s in arrayOfStrings { print(s) }
}
这样使用:
sayStrings("hey", "ho", "nonny nonny no")
可忽略参数
如果一个参数名是下划线 _
那么它就是一个忽略的参数.但是调用的时候是需要给它一个值的. 例如这样一个函数:
func say(_ s:String, times:Int, loudly _:Bool)
其他在函数体内没有使用这个 loudly
名的参数, 但是在调用这个函数的时候还是要传递.
say("hi", times:3,loudly:true)
同样如果我们不希望给它参数名的情况下还是要传递:
func say(_ s:String, times:Int, _:Bool) {
//But the caller must still supply it:
say("hi", times:3, true)
为什么要这样做呢? 虽然编译器并不会抱怨一个参数在函数体内没有使用,我只是用它来做一个标记提醒.
可修改参数
func removeCharacter(_ c:Character, from s: inout String) -> Int { // ① inout 的使用
var howMany = 0
while let ix = s.characters.index(of:c) {
s.remove(at:ix)
howMany += 1
}
return howMany
}
调用的时候:
var s = "hello"
let result = removeCharacter("l", from:&s)
这样,我们通过&
在传递 s
的时候直接使用了它的应用,在函数体内直接修改它的值, 结果就是得到了结果还同时修改了 s 的值.
通常我们会说类是引用类型,而其他的类型是值类型的.当传递一个
struct
的类型作为参数它就是先回拷贝一个值然后传递.
函数里的函数
函数可以定义在任何地方,包括函数体内, 它对外界是完全不可见的. 函数体内定义的函数可以在当前的作用域于之后使用.
这个设计对于函数来说非常漂亮, 因为它可以用来辅助其他的函数,如果一个函数只有一个函数调用那就可以把它最为内部函数写进去.
递归
略
函数作为值
看这段代码:
func doThis(_ f:()->()) {
f()
}
这个 doThis
函数需要一个参数,没有返回值,而参数是一个函数
, 因为有()->()
(无参数无返回值的函数)函数的签名在. 也就是说 doThis
接受一个函数作为参数,然后在内部调用这个函数.
如何具体使用这个 doThis
函数呢, 其实就是给他一个不需要参数没有返回值的函数就好了:
func doThis(_ f:()->()) {
f()
}
func whatToDo() {
print("I did it")
}
doThis(whatToDo)
匿名函数
想想之前的函数作为参数使用的例子,
func whatToAnimate() { // self.myButton is a button in the interface
self.myButton.frame.origin.y += 20
}
func whatToDoLater(finished:Bool) {
print("finished: \(finished)")
}
UIView.animate(withDuration:
0.4, animations: whatToAnimate, completion: whatToDoLater) //①
- ① 这里我们每次都要把函数名字写全了这样是不是很烦.
我们需要匿名函数.
UIView.animate(withDuration:0.4,
animations: {
() -> () in
self.myButton.frame.origin.y += 20
},
completion: {
(finished:Bool) -> () in
print("finished: \(finished)")
}
)
再来个例子感受下:
func drawing() {
let p = UIBezierPath(
roundedRect: CGRect(x:0, y:0, width:45, height:20),
cornerRadius: 8)
p.stroke()
}
let image = imageOfSize(CGSize(width:45, height:20), drawing)
通过匿名函数:
let image = imageOfSize(CGSize(width:45, height:20), {
() -> () in
let p = UIBezierPath(
roundedRect: CGRect(x:0, y:0, width:45, height:20),
cornerRadius: 8)
p.stroke()
})
定义执行一体
swift 中同样提供了一个非常实用的匿名函数写法并且立即执行的写法:
{
// code goes here
}()
有没有点 javascript 里的意思
通常我们都是这样写的:
let para = NSMutableParagraphStyle() //①
para.headIndent = 10
para.firstLineHeadIndent = 10
// ... more configuration of para ...
content.addAttribute( // content is an NSMutableAttributedString
NSParagraphStyleAttributeName,
value:para, //②
range:NSRange(location:0, length:1))
- ①② 都是分别先取值然后赋值
而通过立即执行的写法:
content.addAttribute(
NSParagraphStyleAttributeName,
value: {
let para = NSMutableParagraphStyle()
para.headIndent = 10
para.firstLineHeadIndent = 10
// ... more configuration of para ...
return para
}(),
range:NSRange(location:0, length:1))
闭包
swift 的函数式闭包,也就是说它可以截取到同一个作用域里函数体内的外部变量的引用.
class Dog {
var whatThisDogSays = "woof"
func bark() {
print(self.whatThisDogSays)
}
}
上面的代码意思是说函数 bark
它能够拿到 whatthisdogsays
而这个变量是相对于 bark
的外部的函数体内.因为它定义在想对于它的外部,而他们又是在同一个作用域. 因为在 bark
内部是可以看到这个外部的变量.
而且内部的函数体引用了这个变量. 看上去不错哦, 因为我们知道其实 bark
是可以作为值传递,也就是说它可以从一个环境转移到另外的环境. 发生了什么或者针对 whatThisDogSays
的引用发生了什么变化呢.
func doThis(_ f : (Void) -> Void) {
f()
}
let d = Dog()
d.whatThisDogSays = "arf"
let barkFunction = d.bark
doThis(barkFunction) // arf
执行代码,发现最后得到的结果是 arf. 可能从结果上看并没有什么意外的,但是到底发生了什么, 我们并没有直接去滴啊用d.bark()
,我们实例化了Dog
的实例,然后把它的 bark
函数作为值传递给了 doThis
.
然后调用 doThis
, 而 whatThisdogsays
他是一个 具体的狗的实例的属性, 而在 doThis
的函数体内没有 whatthisdogsays
. 事实上在 doThis 的内部并没有 Dog 实例的存在. 尽管如此调用函数f()
还是可以工作, 也就是函数d.bark
它可以访问到变量 whatthisdogsays
. 即使它自己的执行环境里并没有 Dog 实例或者任何的实例属性 whatthisdogsays.
但是不止于此,如果把d.whatthisdogsays = "arf"
挪到 let barkFunction = d.dark
之后呢, 结果还是一样的.
func doThis(_ f : (Void) -> Void) {
f()
}
let d = Dog() //①
let barkFunction = d.bark //②
d.whatThisDogSays = "arf" // ③
doThis(barkFunction) // arf //④
- ①的时候 d 是一个 Dog 的实例,而这个时候 d.whatthisdogsays 是 woof
- ②的时候把 d.bark 赋值给一个变量
- ③的时候将 whatthisdogsays 赋值新的值
- ④的时候实际是将 d 实例的函数执行了.而 执行的上下文环境就是对应的 d 这个实例.
也就是说②这里的定义的变量barkFunction它是一直维护者对应真实的 Dog (实例)的引用. 而不管后来在什么环境下去代用执行barkFunction,它都维护者对应这个实例的引用.
这里说的截取(capture)
也就是说当将一个函数作为值传递使用的时候,它始终保持着对于外部变量的引用. 这个用法就是将函数作为闭包.
怎么使用闭包来优化代码
函数返回函数
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { // 1
func f () -> UIImage { // 2
let im = imageOfSize(sz) {
let p = UIBezierPath(
roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: 8)
p.stroke()
}
return im
}
return f // 3
}
Let’s analyze that code slowly:
1
The declaration is the hardest part. What on earth is the type (signature) of this function makeRoundedRectangleMaker? It is (CGSize) -> () -> UIImage. That expression has two arrow operators. To understand it, keep in mind that everything after each arrow operator is the type of a returned value. So makeRoundedRectangleMaker is a function that takes a CGSize parameter and returns a () -> UIImage. Okay, and what’s a () -> UIImage? We already know that: it’s a function that takes no parameters and returns a UIImage. So makeRoundedRectangleMaker is a function that takes a CGSize parameter and returns a function — a function that itself, when called with no parameters, will return a UIImage.
2
Now here we are in the body of the function makeRoundedRectangleMaker, and our first step is to declare a function (a function-in-function, or local function) of precisely the type we intend to return, namely, one that takes no parameters and returns a UIImage. Here, we’re naming this function f. The way this function works is simple and familiar: it calls imageOfSize, passing it an anonymous function that makes an image of a rounded rectangle (im) — and then it returns the image.
3
Finally, we return the function we just made (f). We have thus fulfilled our contract: we said we would return a function that takes no parameters and returns a UIImage, and we do so.
让我们来分析一下上面的这段代码先:
- 首选函数名就是
makeRoundedRectangleMaker
, 但是(_ sz:CGSize) -> () -> UIImage
这个怎么解释呢? 首选记住一点的就是每一个剪头的后面都是 ** 返回值**. 所以可以先简单的认为这个函数拿(_ sz:CGSize)
做参数,然后返回了() -> UIImage
, 但是它是什么呢, 我们按照之前说的就是它不要参数而返回一个UIImage
.
重新来, 就是这个函数``makeRoundedRectangleMaker
它拿
(_ sz:CGSize)`作为参数,然后返回来一个 函数, 这个函数呢不需要参数,而返回一个 UIImage.
-
现在我们到了函数
makeRoundedRectangleMaker
体内部,我们第一步就是定义一个函数具有我们想要的返回值, 也就是说不需要参数而返回一个UIImage
, 这里函数名定为f
, 这个函数很简单,它调用iamgeOfSize
函数,然后将它传递给一个匿名函数,它会创建一个圆角的长方形,然后返回这个 image. -
最后我们返回这个函数
f
. 我们已经完成了我们的约定就是返回一个函数,它不需要参数,而返回一个 UIImage.
闭包设置一个捕捉的变量
闭包提供一个捕捉的环境
封装闭包
curried 函数(柯里化函数)
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage {
return {
imageOfSize(sz) {
let p = UIBezierPath(
roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: 8)
p.stroke()
}
}
}
我不喜欢上的写法,因为圆角的值是通过参数 sz
来的,而cornerRadius
确实硬编码进去的. 我想要把它替换掉,这里有2个方法可以事先.第一个方法就是简单地将它作为一个参数传入:
func makeRoundedRectangleMaker(_ sz:CGSize, _ r:CGFloat) -> () -> UIImage {
return {
imageOfSize(sz) {
let p = UIBezierPath(
roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: r)
p.stroke()
}
}
}
所以我们调用的时候可以是这样子的:
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20), 8)
第二种方法就是:
func makeRoundedRectangleMaker(_ sz:CGSize) -> (CGFloat) -> UIImage {
return {
r in
imageOfSize(sz) {
let p = UIBezierPath(
roundedRect: CGRect(origin:CGPoint.zero, size:sz),
cornerRadius: r)
p.stroke()
}
}
}
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20))
self.myImageView.image = maker(8)