0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

swift (2)

Last updated at Posted at 2016-10-12

函数

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)

函数引用和选择器

函数引用和作用域

选择器

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?