iOS编程基础-Swift(二)-函数

Swift入门指南(iOS9 Programming Fundamentals With swift 

第二章 函数

         Swift语法中最具特色也最重要的就是声明和调用函数的方式;

         所有代码都位于函数中,而动作则是由函数触发的;

 

        

print(sum(x: 4, 5))

func sum(x:Int , _ y:Int) -> Int {
    let z = x + y
    return z
}

        

         2.1 函数参数与返回值

         

         基于这个函数,我们分块介绍:

         1.声明从关键字func开始,后跟函数的名字;

         调用函数时必须使用函数名,所谓的调用就是运行函数所包含的代码;

         2.每个参数都有严格的格式:参数名,一个冒号,然后是参数类型;

         3.y之前的下划线和空格,对应的是隐藏对外参数名(之后介绍);

         4.箭头运算符->,后跟函数所返回的值类型;

         5.使用关键字return,后跟返回值;

         关键字return实际上做了两件事:它返回后面的值,同时又终止了函数的执行;

         

         2.1.1 Void返回类型与参数

         

         1.无返回类型参数:

         有三种声明方式:可以返回Void,可以返回(),还可以完全省略箭头运算符与返回类型;

         

         如果函数没有返回值,那么函数体就无须包含return语句;如果包含了return语句,那么目的就纯粹是在该处终止函数的执行;

         Void(无返回值的函数的返回类型)

         ()是一个字面值(后面会介绍字面值()的含义)

         函数声明return()是合法的;无论是否声明,()就是函数所返回的,写成return()或return;(后面加一个分号)有助于消除歧义,否则Swift可能会认为函数会返回下一行的内容;

         

         可以将函数返回值捕获并赋给Void类型的变量;

         

         2.无参数的函数:

         函数声明中的参数列表可以完全为空;

         省略参数列表圆括号本身是不可以的,圆括号需要出现在函数声明中,位于函数名之后;

         

         就像不能省略函数声明中的圆括号(参数列表)一样,你也不能省略函数调用中的圆括号;

 

        

testfunc()

       

func testfunc() -> () {
    return()
}

        

         2.1.2 函数签名

         

         可以通过输入输出的类型来描述一个函数,如 (Int,Int)-> Int ,这是一个合法的表达式,他就是函数签名;

         该签名描述了接收这些类型,具有这些数量的参数,并返回该类型结果的函数;

         函数的签名是其类型——函数的类型;

         函数的签名必须包含参数列表与返回类型,两者为空时,可有四种等价方式,包括Void -> Void 与 () -> ();

         

         2.2 外部参数名

         

         函数可以外化其参数名;

         外部名称要作为实参的标签出现在对函数的调用中;

         原因如下:

         1.阐明了每个实参的作用:每个实参名都能表示出自己对函数动作的作用;

         2.将函数区分开来:两个名字可能有相同的名字和标签,但却拥有不同的外化参数名;

         3.有助于Swift与OC和Cocoa通信,后者的方法参数几乎总是有外化名字的;

         

         外化参数名:将外部名字放在内部参数名之前,中间用逗号隔开;

         

         标准规则:默认下,除第一个参数外,所有参数名都会自动外化(实际上是所有的参数均自动外化);

         原因有二:

         1.第一个参数通常不需要外部名,因为函数名通常能够清晰的表明第一个参数的含义;

         2.这个约定使得Swift函数能够与OC方法交互,而后者采取的就是这种方式;

         

         Swift的String和OC的NSString能够自动桥接彼此;

         

         如果函数是自己定义的(即是你声明的),并且OC永远不会调用它,这样就没必要遵循OC的要求了;

         

         外化参数名,将外部名放到内部名之前即可,可相同;

         若不想对参数进行外化,可在内部名之前加一个下划线和一个空格;

 

        sum(x: 3, 4)
        sum(3, y: 4)
        sum(xx: 3, yy: 4)

        

func sum(_ x:Int , y:Int) -> Int {
    let z = x + y
    return z
}

func sum(xx x:Int , yy y:Int) -> Int {
    let z = x + y
    return z
}

        

         什么是函数的名字:

         从技术上来说,一个Swift函数的名字是由圆括号之前的名字加上参数的外部名共同构成的;如果阻止了参数的外部名,那么可以使用一个下划线来表示其外部名;

         

         2.3 重载

         

         在Swift中,函数重载是合法的:具有相同函数名(包括外部参数名)的两个函数可以共存,只要签名不同即可;

         重载也适用于返回类型:具有相同名字与相同参数类型的两个函数可以有不同的返回类型;不过调用上下文一定不能有歧义;也就是说,一定要清楚调用者需要什么样的返回类型;

         

         OC是不允许重载的:如果OC中声明了相同的方法的两个重载版本,那么编译器就会报“Duplicate declaration”错误;

         实际上,如果在Swift中声明了两个重载方法,但是OC却能看到它们,那么就会遇到一个Swift编译错误,因为这种重载与OC是不兼容的;

         

         具有相同标签和不同外部参数名的两个函数并不构成重载;由于函数有着不同的外部参数名,因此他们是名字不同的两个不同函数;

         

         2.4 默认参数值

         

         参数可以有一个默认值:调用者完全可以省略参数,不为其提供实参,对应参数的值就是默认值;

         要提供默认值,在声明中的参数类型后追加一个=号和默认值;

         

         请保证调用时实参的顺序与声明时形参的顺序一致;

 

        saySome("woof")
        saySome("woof", times: 2)

        

func saySome(_ word:String , times:Int = 3) -> Void {
    print("say \(word) \(times)times")
}

        

         2.5 可变参数

         

         参数可以是可变参数:调用者可以根据需要提供多个参数类型的值,中间用逗号隔开;函数体会将这些值当做数组;

 

        

sayStrings(strings: "a","b","c","d", times: 3)

        

func sayStrings(strings:String... , times:Int = 1) -> Void {
    for _ in 1...times {
        for str in strings {
            print(str + " print", separator: ",", terminator: ",")
        }
        print("\n")
    }
}

         一个函数最多只能声明一个可变参数(否则就无法确定值列表结束的位置)

         全局print函数的第一个参数就是一个可变参数;

         

         遗憾的是,Swift语言有一个陷阱:没法将数组转换为逗号分隔的参数列表(相比于Ruby中的splat)。如果一开始就有一个某种类型的数组,那么你不能在需要该类型可变参数的地方使用它;

         

         2.6 可忽略参数

         

         局部名为下划线的参数会被忽略,由于没有内部名,也无法在函数体中被引用,仅作为对自己的提示“我知道这里有个参数,只不过故意不用”

         

         2.7 可修改参数

         

         如果希望函数能够修改传递给他的实参的初始值,需要作出三种改变:

         1.要修改的参数必须声明为inout;

         2.在调用时,持有待修改值的变量必须要声明为var,而不是let;

         3.相比于将变量作为实参进行传递,我们传递的是地址,这是通过在名字前面加上&符号做到的;

         

        var s = "hello"
        let result = removeFromString(s: &s, charater: Character("l"))
        print(result,s)

 
func removeFromString( s:inout String , charater c:Character) -> Int {
    var  howMany = 0
    while let ix = s.characters.index(of: c) {
        s.removeSubrange(ix...ix)
        howMany += 1
    }
    return howMany;
}


         当调用具有inout参数的函数时,地址作为实参传递给参数的变量总是会被设定;

         

         在使用Cocoa时,会遇到该模式的变种:

         Cocoa的API是使用C与OC编写的,因此看不到Swift术语inout;你可能会看到一些奇怪的类型,如UnsafeMutablePointer;不过从调用者的视角来看,它们是一回事;依然是准备var变量并传递其地址;

         这种可以应用在多返回值的情况,函数本身不返回值,而是将对应实参的地址传过来,通过地址修改它们,这样他们就是操作的结果了;

         

         有时当参数是某个类的实例时,函数需要修改这个没有声明为inout的参数;

         这是类的一个特殊特性,其他两种对象类型(枚举和结构体)风格不同;String不是类,是结构体;

         

         对于类的实例:

 

       

 let d = Dog()
        d.name = "Fido"
        changeNameOfDog(d: d, to: "Rover")

class Dog {
    var name = ""
}

func changeNameOfDog(d:Dog , to toString:String){
    d.name = toString
}

        

         并没有将Dog实例d最为inout参数传递;d一开始使用let而非var进行声明;但我们依然可以修改它的属性

         这是因为:

         类实例的一个特性,即实例本身是可变的;

         

         从技术上说,类是引用类型,而其他对象类型风格则是值类型;

         在将结构体的实例作为参数传递给函数时,实际上使用的是该结构体实例的一个独立的副本(所以要用inout修饰,改为传地址);

         不过,在将类实例作为参数传递给函数时,传递的则是实例本身;

         

         2.8 函数中的函数

         

         可以在任何地方声明函数,包括在函数体中声明;

         声明在函数体中的函数(也叫局部函数)可以被相同的作用域的后续代码调用,不过在作用域之外则完全不可见;

         

         

         对于那些旨在辅助其他函数的函数,这是个又优雅的架构;如果只有函数A需要调用函数B,那么函数B就可以放在函数A中;

         

         有时,即便函数只会在一个地方调用,使用局部函数也是值得的;结构清晰;

         局部函数说明做什么,外部作用域去做;

         

         局部函数就是带有函数值的局部变量;因此,局部函数不能与相同作用域中的局部变量同名,相同作用域中的两个局部函数也不能同名;

         

         2.9 递归

         

         函数调用自身

 

        

countDownFrom(ix: 5)

func countDownFrom(ix:Int){
    print(ix)
    if ix > 0 {
        countDownFrom(ix: ix - 1)
    }
}

        

        2.10 将函数作为值

         

         在Swift中,函数时一等公民,这意味着函数可以用在任何可以使用值的地方;

         函数可以赋给变量;函数可以作为函数调用的参数;函数可以作为函数的结果返回;

         

         函数是有类型的,函数的签名就是其类型;

         

         将函数调用封装到函数中(作为参数传递给其他函数,然后调用),这样做是很有价值的,因为其他函数可以以特殊的方式来调用参数函数;比如完成或之后调用;

         

         举例:Cocoa中的绘图

 

        

let size = CGSize.init(width: 45, height: 20)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        let p = UIBezierPath.init(roundedRect: CGRect.init(x: 0, y: 0, width: 45, height: 20), cornerRadius: 8)
        p.stroke()
        _ = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        

         这样做十分丑陋;

         所有的代码的唯一目的就是获取result,即图像;

         

         每次绘制时唯一不同的就是步骤2,只需编写一个副主函数表示出样板化过程就能解决问题;

 

        func drawing(){
            let p = UIBezierPath.init(roundedRect: CGRect.init(x: 0, y: 0, width: 45, height: 20), cornerRadius: 8)
            p.stroke()
        }
        
        _ = imageOfSize(size: CGSize.init(width: 45, height: 20), whatToDraw: drawing)

func imageOfSize(size:CGSize , whatToDraw:()->()) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    whatToDraw()
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result
}


        

         这是将绘制指令转换为图像的一种漂亮的方式

         

         OC中的block块语法,在Swift中就是个函数,将其当做函数并传递就可以了;

         

         为了让函数类型说明符更加清晰,请通过Swift的typealias特性创建一个类型别名,为函数类型赋予一个名字;这个名字可以是描述性的,请不要与箭头运算符符号搞混;

         如果定义typealias VoidVoidFunction = ()-> (),那就可以通过改签名指定函数类型是使用VoidVoidFunction;

         

         2.11 匿名函数

         

         匿名函数:不需要函数名,传递时只传递函数的函数体即可;

         

         构造匿名函数,你需要完成两件事:

         1.创建函数体本身,包括外面的花括号,但不需要函数声明;

         2.如果必要,将函数的参数列表和返回值类型作为花括号的第一行,后跟关键字in;

         

         Swift提供了一些便捷写法:

         1.省略返回类型:

         如果编译器知道匿名函数的返回类型,那么你就可以省略箭头运算符及返回类型说明;

         2.如果没有参数,那么可以省略in这一行:

         如果匿名函数不接收参数,并且返回类型可以省略,那么in这一行就可以被完全省略;

         3.省略参数类型:

         如果匿名函数接受参数,并且编译器知道其类型,那么类型就可以省略;

         4.省略圆括号:

         如果省略参数类型,那么包围参数列表的圆括号也可以省略;

         5.有参数时也可以省略in这一行:

         如果返回类型可以省略,并且编译器知道参数类型,那就可以省略in这一行,直接在匿名函数体中引用参数,方式是使用魔法名$0、$1等,并且要按顺序引用;

         6.省略参数名:

         如果匿名函数体不需要引用某个参数,那就可以在in这一行通过下划线来代替参数列表中该参数的名字;事实上,如果匿名函数体不需要引用任何参数,那就可以通过一个下划线来代替整个参数列表;

         

         不过注意:如果匿名函数接收参数,那就必须要以某种方式承认他们的存在,可以省略in这一行,然后通过魔法名$0等来使用参数,或是保留in这一行,然后通过下划线省略参数,但不能在省略in这一行的同时又不通过魔法名来使用参数,否则,代码编译不过;

         

         7.省略函数实参标签:

         如果匿名函数是函数调用的最后一个参数,那么你可以在最后一个参数前通过右圆括号关闭函数调用,然后放置匿名函数体且不带任何标签(这叫尾函数)

         8.省略调用函数圆括号:

         如果使用尾函数语法,并且调用的函数只接收传递它的参数,那就可以在调用中省略空的圆括号;这是唯一一个可以从函数调用中省略圆括号的情形;

         9.省略return关键字:

         如果匿名函数体中只包含一条语句,并且该语句使用关键字return返回一个值,那么关键字return就可以省略;

         

         示例:

         数组map方法接收一个函数,该函数接收一个参数,并且返回一个与数组元素相同类型的值;

 

        let arr1 = [2,4,6,8]
        let arr2 = arr1.map{$0*2}
        print(arr2)

        

         示例中,返回类型已知,参数类型已知,只有一个参数,省略in一行,使用魔法名$0引用参数,只有一条语句,作为return语句,map不接收其他参数,因此省略圆括号,在名字后直接跟着尾函数即可;

         

         2.12 定义与调用

         

         Swift中非常常见的一种模式模式就是定义一个匿名函数然后调用它

 

        let funcPara = {
            (a:Int , b:Int) in
            return a + b
        }(3,4)
        
        print(funcPara)

        

        

         注意花括号后面的圆括号;花括号定义一个匿名函数体,圆括号则调用了这个匿名函数

         

         动作在必要的时候发生(函数在必要的时候调用),而不必依赖于一系列准备步骤(一系列配置),把这些步骤放到匿名函数体中,然后调用返回实例,作为动作的参数即可;


        

        self.view.addSubview({
            (rect:CGRect) -> UIView in
           let view = UIView.init(frame: rect)
            view.backgroundColor = UIColor.red
            return view
            }(CGRect.init(x: 0, y: 0, width: 20, height: 30))
        )

        

         2.13 闭包

         

         Swift函数就是闭包:这意味着他们可以在函数体作用域中捕获对外部变量的引用;

         

         函数在传递时会持有其所在的环境,甚至在传递到另一个全新环境中再调用时也如此;

         对于“捕获”,是当函数作为参数传递时,他会持有对外部变量的内部引用,这使得函数成为一个闭包;

         被捕获的引用由函数维护,当函数被调用时,可以使用该引用指向的对象;

         

         2.13.1 闭包是如何改善代码的

 

self.view.addSubview({
           let imgView = UIImageView.init(frame: CGRect.init(x: 100, y: 100, width: 50, height: 60))
            imgView.image = makeRoundedRectangle(sz: CGSize.init(width: imgView.frame.size.width, height: imgView.frame.size.height))
            return imgView
        }())

func makeRoundedRectangle(sz:CGSize) -> UIImage? {
    let image = imageOfSize(size: sz){
        let p = UIBezierPath.init(roundedRect: CGRect.init(origin: CGPoint.zero, size: sz), cornerRadius: 8)
        p.stroke()
    }
    return image
}


         func imageOfSize(size:CGSize , whatToDraw:()->()) -> UIImage? ;函数调用时,尾函数语法传递的是一个匿名闭包,该闭包捕获了外部函数 func makeRoundedRectangle(sz:CGSize) -> UIImage? ;的sz参数的引用,这样做代码更加通用,也变得更紧凑了;

         

         2.13.2 返回函数的函数

         依照前面的例子;

         相对于返回一张图片,函数可以返回一个函数,这个函数可以创建指定大小的圆角矩形

 

        let maker = makeRoundedRectangleMaker(sz: CGSize.init(width: 100, height: 200))
        self.view.addSubview({
            let imgView = UIImageView.init(frame: CGRect.init(x: 100, y: 100, width: 100, height: 100))
            imgView.image = maker()
            return imgView
            }())

func makeRoundedRectangleMaker(sz:CGSize) -> () -> UIImage? {
    return {
        return imageOfSize(size: sz){
            let p = UIBezierPath.init(roundedRect: CGRect.init(origin: CGPoint.zero, size: sz), cornerRadius: 8)
            p.stroke()
        }
    }
}


        

         注意函数makeRoundedRectangleMaker的声明:请记住每个箭头运算符后面的内容就是返回值的类型;

         

         makeRoundedRectangleMaker类似于一个工厂,用于创建类似于maker的一系列函数,其中每个函数都会生成特定尺寸的一张图片,这是对闭包功能的最好说明:捕获了特定尺寸,调用即可生成图片;

         

         2.13.3 使用闭包设置捕获变量

         

         闭包可以捕获其环境的能力甚至要超过之前所介绍的,如果闭包捕获了对外部变量的引用,并且该变量的值是可以修改的,那么闭包就可以设置该变量;

         

         2.13.4 使用闭包保存捕获的环境

         

         当闭包捕获其环境后,即便什么都不做,他也可以保存该环境;

         举一个比较经典的例子:一个可以修改函数的函数;

 

        let countedGreet = countAdder {
            print("hello dm")
        }
        countedGreet()
        countedGreet()
        countedGreet()

func countAdder(f:@escaping () -> ()) -> () -> () {
    var ct = 0
    return {
        ct = ct + 1
        print("count is \(ct)")
        f()
    }
}

        

         关于@escape标注的使用:一个被保存在某个地方等待稍后(比如函数返回以后)再调用的闭包就叫做逃逸闭包(传递给map的闭包会在map中同步执行,是非逃逸的)

         

         countAdder向传递给它的函数增加了调用次数的功能;

         ct故意声明在了所返回的匿名参数外,这样做,ct只会被初始化为0一次,然后会由匿名函数所捕获,这样,该变量就会被保存为counterGreet环境的一部分;

         

         同时这个示例(可以保存环境状态)还有助于说明函数是引用类型;

         赋值或是参数传递就会生成对相同函数的新引用;

         

         2.14 柯里化函数

         

         在之前的示例中,生成图片的cornerRadius是硬编码,我们希望能够制定圆角半径,可以使用下面的方式:

         现在makeRoundedRectangleMaker所返回的函数不接受参数,我们可以让它接收一个参数;

 

        

        self.view.addSubview({
            let imgView = UIImageView.init(frame: CGRect.init(x: 200, y: 100, width: 80, height: 80))
            imgView.image = makeRoundedRectangleMakerKLH(sz: CGSize.init(width: 80, height: 80))(10)
            return imgView
            }())

func makeRoundedRectangleMakerKLH(sz:CGSize) -> (CGFloat) -> UIImage? {
    return {
        cornerRadius in
        return imageOfSize(size: sz){
            let p = UIBezierPath.init(roundedRect: CGRect.init(origin: CGPoint.zero, size: sz), cornerRadius: cornerRadius)
            p.stroke()
        }
    }
}


        

         函数返回的函数接收一个参数就叫做柯里化函数;

 

你可能感兴趣的:(iOS-编程基础(Swift))