Swift 3.0 从 ++ 的实现到 inout 和 defer 的小细节

 

Swift 3.0 中删去了原 C Style 的自加和自减写法,转而推荐使用+=和-=写法。有时我们会在数组下标处直接修改某些计数值,而+=写法是个表达式,本身是不返回值的,因此无法代替原来的功能。...

(点击上方公众号,可快速关注)

来源: Enum (@提拉拉拉就是技术宅 )

www.jianshu.com/p/5e309c4908ce

如有好文章投稿,请点击 → 这里了解详情

本文是一个在 Swift 3.0 中自加和自减的实现

项目在我的「Playground」(http://enumsblog.com/playground?sample=enum_posts_17004_1)中开源

引言

Swift 3.0 中删去了原 C Style 的自加和自减写法,转而推荐使用+=和-=写法。有时我们会在数组下标处直接修改某些计数值,而+=写法是个表达式,本身是不返回值的,因此无法代替原来的功能。

本文将通过重载运算符来找回自加和自减,顺便提及一下inout和defer的小知识。

正文

自加和自减分别包含了两个运算符,共四个运算符。即:++前置/后置、–前置/后置。

前置运算符很简单,因为是先自加,再返回自加后的值:

@discardableResult

prefix func ++(x: inout Int) -> Int{

x += 1

returnx

}

测试时直接前置自加是可以实现功能的,这里会打印出2,即数组第二个元素:

vari = 0

let list = [1,2,3,4]

print(list[++i])

但后置运算符不同,是先使用本身的值,使用完以后再进行自加,因此它似乎应该长这样:

@discardableResult

postfix func ++(x: inout Int) -> Int{

lety = x

x += 1

returny

}

但事实上,可以使用defer关键字推迟加法运算,可以写成这样:

@discardableResult

postfix func ++(x: inout Int) -> Int{

defer{

x += 1

}

returnx

}

defer代码块会被压入栈中,待函数结束时弹出栈运行。

代码看上去怪怪的,这样真的没问题吗?defer和return哪个会被先执行呢?x 是inout的,不论是哪个先被执行,结果返回的不都是应该返回已经 +1 的 x 吗?

其实这里包含了两个问题,一是inout究竟怎样工作的,二是defer究竟是怎样工作的。

inout

inout在写法上与C语言传递地址的写法十分类似,在调用函数传入参数是带有前缀&,就好像取地址传进去了一样,实则不然。

Swift 中 struct 是值类型的。

何为值,值是不能改变的,是immutable的,任何对值的修改其实就是新构造了一个来替换原来的。这里的inout也是如此,并不是传了地址进来,而是在这里构造了一个新的结构体,当函数结束时会复制回去替换原来的结构体。

当然,这个复制并不一定会真的复制。Swift 的copy on write也会分情况,当值类型的引用只有一个时是不会复制的,这段在猫神的书里有提到。

defer

这里defer代码块会被压入栈中,函数结束时执行。到底啥时候执行呢?是在return后执行,如果return调用了其他函数,这个函数会在defer代码块执行之前被执行。这个简单测试一下即可,也可以直接看汇编,上面代码的汇编长这样:

SelfPlusDemo`++ postfix(inout Int) -> Int:

0x100001c60 :  pushq  %rbp

0x100001c61 :  movq   %rsp, %rbp

0x100001c64 :  subq   $0x10, %rsp

0x100001c68 :  movq   %rdi, -0x8(%rbp)

0x100001c6c : movq   (%rdi), %rax

0x100001c6f : movq   %rax, -0x10(%rbp)

//在这里调用了defer代码块,首地址为0x100001c90

0x100001c73 : callq  0x100001c90               ;SelfPlusDemo.(++ postfix(inout Swift.Int) -> Swift.Int).($defer#1) () -> () at main.swift

0x100001c78 : movq   -0x10(%rbp), %rax

0x100001c7c : addq   $0x10, %rsp

0x100001c80 : popq   %rbp

0x100001c81 : retq

回到上面自加的代码中。

这里defer代码块会被压入栈中,return 时还没有被执行,因此这里的值是正是我们想要的自加以前的值。按照inout的原理,这个 x 指向的不是函数外面的 x,这只是个临时变量,在defer代码块执时值被修改了也不会影响返回值,因为至始至终都是值,不是引用,在函数结束之后会把这个临时变量拷贝回去,改变函数外的 x 值。

因此整个流程都是在操作和复制值,都是immutable的,不存在互相影响的可能,因此这段代码能像想象中那样工作。

按照这个思路完成自减代码即可。完整代码在我的「Playground」中开源。几句简单的代码基本能够代替C语言的自加自减功能了。

Swift 中测试结果为:



对于最后一个比较复杂的表达式,可以看出 Swift 跟C语言一样也是从右往左计算的,计算结果跟C语言中的是一致的:



结语

其实 Swift 3.0 去除自加和自减也是有原因的,在非必要的情况下还是尽量使用推荐的写法为好,这习惯确实在对代码可读性没什么好处。养成良好编码习惯从我做起,这段代码也仅供特殊情况下使用。
觉得本文对你有帮助?请分享给更多人
关注「 iOS大全 」
看更多精选 iOS 技术文章


    关注 iOS大全


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册