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

前言:

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函数的总结,如有不足之处,欢迎大家指正。


作者 | 奔跑的不将就

来源 | 掘金

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,786评论 6 534
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,656评论 3 419
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,697评论 0 379
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,098评论 1 314
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,855评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,254评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,322评论 3 442
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,473评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,014评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,833评论 3 355
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,016评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,568评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,273评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,680评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,946评论 1 288
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,730评论 3 393
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,006评论 2 374

推荐阅读更多精彩内容