美文网首页
iOS开发——Swift中的函数盘点

iOS开发——Swift中的函数盘点

作者: iOS开发之家 | 来源:发表于2021-11-18 17:01 被阅读0次

    前言:

    Swift已经被越来越多的公司使用起来,因此Swift的学习也应该提上日程了。本篇就先探索Swift中的函数,主要包括以下几个方面:

    • Swift函数定义

    • Swift函数参数与返回值

    • Swift函数重载

    • 内敛函数优化

    • 函数类型、嵌套函数

    一、Swift函数定义

    函数的定义包含函数名、函数体、参数及返回值,定义了函数会做什么、接收什么以及返回什么。函数名前要加上 func 关键字修饰。如下为一个完整的函数定义事例:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
    }

    </pre>

    • 函数名:greet

    • 参数:圆括号中(person: String)即为参数,person为参数名,String为类型

    • 返回值:使用一个 -> 来明确函数的返回值,在该事例中定义了一个 String类型的返回值

    如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:https://gitee.com/Mcci7/i-oser 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

    二、函数返回值与参数

    2.1 函数返回值

    从返回值的角度看,函数可以分为有返回值和无返回值两种。无返回值的函数可以有如下三种定义方式:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func testA() -> Void {
    }

    func testB() -> () {
    }

    func testC() {
    }

    let a = testA()
    let b = testB()
    let c = testC()

    </pre>

    打印 a、b、c 可以发现,三者的类型均为(),即空元组。在 Void 的定义处也可以发现,Swift中 Void 就是空元组。

    [图片上传中...(image-1a4ab5-1637226038732-4)]

    也就是说上面三种方式是等价的,都表示无返回值的情况,不过从代码简洁程度上来说,最后一种更方便使用。

    还有一种函数有返回值的情况,如同第一节中所述的函数定义方式,即为一种返回值为String的函数。在Swift中,函数的返回值可以隐式返回,如果函数体中只有一句返回代码,则可以省略return关键字。如下代码所示,两种写法是等价的:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func testD() -> String {
    return "正常返回"
    }
    func testE() -> String {
    "隐式返回"
    }

    </pre>

    Swift中还可以通过元组实现多个返回值的情况,如下所示:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func compute(a:Int, b: Int) -> (sum: Int, difference: Int) {
    return (a+b, a-b);
    }

    </pre>

    compute函数返回一个元组,包含了求和与求差,实现了返回多个值的情况。

    2.2 函数参数

    与OC不同的是,Swift中函数的参数是let修饰的,参数值是不支持修改的。如下图所示,可以证明。

    [图片上传中...(image-9dbe10-1637226038734-20)]

    2.2.1 函数标签

    Swift的函数参数除了形参外,还包含一个参数标签。形参在函数内部使用,使得函数体中使用没有歧义,而函数标签用于在函数调用时使用,其目的是增加可读性。函数标签是可以省略的,使用_表示即可,需要注意的是,_与不设置函数标签是不一样的,如下图所示:

    [图片上传中...(image-68984b-1637226038734-19)]

    图片

    当使用_时,调用函数不会显示函数标签,而不设置函数标签会把形参作为函数标签。

    2.2.2 函数默认参数值

    Swift可以给函数参数设置默认值,设置了默认值的参数,在函数调用时可以不传参

    [图片上传中...(image-339f1f-1637226038734-17)]

    由于参数 a 有了默认值 8,所以在调用时只传参 b 就可以。同样的,如果参数均有默认值,则在调用函数时,都可以不传值。

    图片

    如图所示,由于两个参数均有默认值,在调用时都不传值,就像调用了一个无参函数一样。

    Swift中设置函数参数默认值可以不按照顺序,因为Swift中有函数标签,不会造成歧义。而在C++中,则必须要按照从右往左的顺序依次设置,两者对比如下:

    [图片上传中...(image-3f5b28-1637226038734-15)]

    [图片上传中...(image-cd1ebe-1637226038734-14)]

    下面一张图是C++的调用,没有按照顺序设置默认值,直接报错缺失b的默认值,而Swift中则不会。但是,如果Swift函数参数都隐藏了函数标签,则无法识别是给哪个参数,只能按照从右往左的方向赋值,这样就会照成报错,如下图所示:

    [图片上传中...(image-c7063e-1637226038734-13)]

    在调用函数时,直接报错缺失第二个参数。因此,在Swift中,如果省略了函数参数标签,要保证所有的函数参数都有值,或者都可以得到赋值。

    2.2.3 可变参数

    与OC的NSLog参数一样,Swift函数也提供了可变参数,其定义方式是 参数名:类型...,可以参照系统的print函数定义:

    [图片上传中...(image-25b3a8-1637226038734-12)]

    print函数的第一个参数即为可变参数,参数类型为Any,可以接受任意类型,输入时以,分割即可。

    可变参数需要注意的一点是,在紧随其后的一个参数不能省略参数标签,如下图所示:

    [图片上传中...(image-e7f40e-1637226038734-11)]

    参数b也是一个Any类型,如果省略了参数标签,则在调用函数时就没有了标签区分,仅凭,编译器无法确定该将参数赋值给item还是b,因此会报错。

    可变参数本质上是一个数组,可以在函数内部使用参数,查看其类型如下:

    [图片上传中...(image-e5c4fd-1637226038732-3)]

    可以看到 item 实际上是一个 Any 类型的数组。

    2.2.4 inout修饰的参数

    在OC和C中,我们可以通过指针传参,以达到在函数内部修改函数外部实参的值的目的。在Swift中,也提供了类似的方法,不过需要使用inout修饰一下参数,具体使用方式如下:

    [图片上传中...(image-f594cb-1637226038734-10)]

    number的值本来为10,经过inoutFunc函数调用,结果变为了20。那么 inout 是如何改变了外部实参的值的呢?有种说法是与OC一样,采用了指针传值的方式改变;还有说法是 inout 在底层是一个函数,将其修饰的函数内部的值通过这个函数重新赋值外部实参。

    针对这两种说法,我们可以通过汇编来验证下,本次使用的是真机调试,因此使用的是ARM下的汇编。

    将上图中12行22行的断点打开,并打开XCode的汇编调试 Debug -> Debug Workflow -> Always show Disassembly。运行工程,首先进入22行的断点:

    [图片上传中...(image-311e93-1637226038732-2)]

    图中红框处为 inoutFunc 函数的调用处,在上面28行可以发现一行代码 ldr x0, [sp, #0x10]

    这句代码的意思是,将[sp, #0x10]的值赋值给 x0 寄存器,[sp, #0x10]表示 sp+#0x10的地址,也就是说 x0 寄存器现在存储的是一个地址,通过 register read x0 命令可知改地址为 x0 = 0x000000016dbf9a80

    单步调试进入 inoutFunc 函数,得到如下代码:

    [图片上传中...(image-22c0cf-1637226038733-9)]

    执行到第4行,再次读取 x0 寄存器得到了相同的值x0 = 0x000000016dbf9a80,此时通过 x/4gx 读取内存地址0x000000016dbf9a80的值,得到结果如下:

    [图片上传中...(image-497160-1637226038733-8)]

    红框中的值 0x000000000000000a 换算成十进制正是 10。走到第6行汇编代码,将x0存储的地址所指向的内容存到x8寄存器,然后将值加10,就此完成对外部实参值的改变。在viewDidLoad中调用inoutFunc后并没有对于number的重新赋值,也证实了inout是通过地址传递改变外部实参的值。

    使用inout需要注意两点:

    • 1、inout只能传入可以被多次赋值的,即不能传入常量和字面量

    • 2、inout不能修饰可变参数

    三、函数重载

    函数重载指的是函数名相同,但是参数名称不同 || 参数类型不同 || 参数个数不同 || 参数标签不同。需要注意的是,函数重载(overload)与函数重写(override)是两个概念,函数重写涉及到继承关系,而函数重载不涉及继承关系。另外,在OC中没有函数或方法的重载,只有重写。以下是几个函数重载的例子:

    [图片上传中...(image-8fa73d-1637226038733-7)]

    可以看到,四个函数的方法名称相同,但是参数不同,实际上并不会报错,这就是方法重载。

    不过方法重载也有需要注意的地方:

    • 方法重载与函数返回值无关,即函数名及参数完全相同的情况下,如果返回值不同,不构成函数重载,编译器会报错。

    [图片上传中...(image-bc4706-1637226038733-6)]

    如图所示,在调用方法时,编译器不知道该调用哪个函数,因此会报二义性错误。

    • 方法重载与默认参数值的情况

    [图片上传中...(image-ad1c00-1637226038733-5)]

    从图中可以发现,由于第二个函数给参数c设置了默认值,在调用时形式上与第一个函数一样,不过编译器在此并不会报错,猜想是因为第二个函数还有一种test(a: , b: , c: )的调用形式。

    四、inline内联函数

    内联函数,其实是指开启了编译器内联优化后,编译器会将某些函数优化处理,该优化会将函数体抽离出来直接调用,而不会给这个函数再开辟栈空间。

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func test() {
    print("test123")
    }
    test()

    </pre>

    如以上函数所示,调用test()时,需要为其开辟栈空间,而其内部只调用了一个print函数,所以在开启内联优化的情况下,可能会直接调用print函数。

    开启内联优化的方式如下图:

    [图片上传中...(image-2a6d5b-1637226038731-1)]

    Debug模式下默认不开启优化,Release模式下默认是开启的。为了测试内联优化的现象,这里先将Debug模式开启优化,之后在test()调用处打断点,再运行工程会发现,直接打印了test123,然后在test函数内部打断点,进入汇编如下:

    [图片上传中...(image-d8efac-1637226038731-0)]

    全局搜索发现没有test函数的调用,而是直接调用了print函数。

    不过内联优化,也不是对所有函数都会进行优化,以下几点不会优化:

    • 函数体代码比较多

    • 函数存在递归调用

    • 函数包含动态派发,例如类与子类的多态调用

    内联函数还有内联参数控制@inline(never)@inline(__always)

    • 使用@inline(never)修饰,即使开启了编译器优化,也不会内联

    • 使用@inline(__always)修饰,开启编译器优化后,即使函数体代码很长也会内联,但是递归和动态派发依然不会优化

    五、函数类型

    每一个函数都可以符合一种函数类型,例如:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func test() {
    print("test123")
    }

    对应 () -> ()

    func compute(a:Int = 8, b: Int = 9) -> Int {
    return a+b;
    }

    对应 (Int, Int) -> Int

    </pre>

    上述代码中,() -> ()(Int, Int) -> Int都表示一种函数类型。可以发现函数类型是不需要参数名的,直接标明参数类型即可。

    函数类型也可以用作函数的参数和返回值,使用函数类型作为返回值的函数被称为高阶函数,例如:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    // 函数类型作为参数
    func testFunc(action:(Int) -> Int) {
    var result = action(2)
    print(result)
    }

    func action(a:Int) -> Int {
    return a
    }
    testFunc(action: action(a:))

    // 函数类型作为返回值
    func action(a:Int) -> Int {
    return a
    }
    func testFunc() -> (Int) -> Int {
    return action(a:)
    }
    let fu = testFunc()
    print(fu(3))

    </pre>

    六、嵌套函数

    Swift中,可以在函数内部定义函数,被称为嵌套函数,如下代码所示:

    <pre style="margin: 16px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(51, 51, 51); font-size: 1em; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; line-height: 1.75; box-shadow: rgba(110, 110, 110, 0.45) 0px 0px 8px; border-radius: 4px; background: rgb(248, 248, 248);">

    func forward(_ forward: Bool) -> (Int) -> Int {
    func next(_ input: Int) -> Int {
    input + 1
    }
    func previous(_ input: Int) -> Int {
    input - 1
    }

    return forward ? next : previous
    

    }

    </pre>

    像上面这样在函数内部定义其他的函数,其目的是为了将函数内部的实现封装起来,外部只看到调用了 forward,而不需要知道其内部的实现逻辑,当然也不能直接调用内部的嵌套函数。

    总结

    相对于OC,Swift中主要增加了以下几点:

    • 参数标签

    • 函数重载

    • 嵌套函数

    整体而言,个人感觉Swift的函数使用起来更加方便,参数标签使得代码可读性更强。以上即为本篇关于Swift函数的总结,如有不足之处,欢迎大家指正。


    作者 | 奔跑的不将就

    来源 | 掘金

    相关文章

      网友评论

          本文标题:iOS开发——Swift中的函数盘点

          本文链接:https://www.haomeiwen.com/subject/dvxjtrtx.html