swift中defer几个简单的使用场景详解
前言
最近准备把swift文档再扫一遍,发现了defer这个关键字,defer是个非常重要的swift语言特征,恕本人愚钝,以前还从来没有用过这个呢~简单地列一下这个东西有哪些可以用得上的情景吧~~话不多说了,来一起看看详细的介绍吧。
defer是干什么用的
很简单,用一句话概括,就是deferblock里的代码会在函数return之前执行,无论函数是从哪个分支return的,还是有throw,还是自然而然走到最后一行。
这个关键字就跟Java里的try-catch-finally的finally一样,不管trycatch走哪个分支,它都会在函数return之前执行。而且它比Java的finally还更强大的一点是,它可以独立于trycatch存在,所以它也可以成为整理函数流程的一个小帮手。在函数return之前无论如何都要做的处理,可以放进这个block里,让代码看起来更干净一些~
下面是swift文档上的例子:
varfridgeIsOpen=false
letfridgeContent=["milk","eggs","leftovers"]
funcfridgeContains(_food:String)->Bool{
fridgeIsOpen=true
defer{
fridgeIsOpen=false
}
letresult=fridgeContent.contains(food)
returnresult
}
fridgeContains("banana")
print(fridgeIsOpen)
这个例子里执行的顺序是,先fridgeIsOpen=true,然后是函数体正常的流程,最后在return之前执行fridgeIsOpen=false。
几个简单的使用场景
trycatch结构
最典型的场景,我想也是defer这个关键字诞生的主要原因吧:
funcfoo(){
defer{
print("finally")
}
do{
throwNSError()
print("impossible")
}catch{
print("handleerror")
}
}
不管doblock是否throwerror,有没有catch到,还是throw出去了,都会保证在整个函数return前执行defer。在这个例子里,就是先print出"handleerror"再print出"finally"。
doblock里也可以写defer:
do{
defer{
print("finally")
}
throwNSError()
print("impossible")
}catch{
print("handleerror")
}
那么它执行的顺序就会是在catchblock之前,也就是先print出"finally"再print出"handleerror"。
清理工作、回收资源
跟swift文档举的例子类似,defer一个很适合的使用场景就是用来做清理工作。文件操作就是一个很好的例子:
关闭文件
funcfoo(){
letfileDescriptor=open(url.path,O_EVTONLY)
defer{
close(fileDescriptor)
}
//usefileDescriptor...
}
这样就不怕哪个分支忘了写,或者中间throw个error,导致fileDescriptor没法正常关闭。还有一些类似的场景:
dealloc手动分配的空间
funcfoo(){
letvaluePointer=UnsafeMutablePointer.allocate(capacity:1)
defer{
valuePointer.deallocate(capacity:1)
}
//usepointer...
}
加/解锁:下面是swift里类似Objective-C的synchronizedblock的一种写法,可以使用任何一个NSObject作lock
funcfoo(){
objc_sync_enter(lock)
defer{
objc_sync_exit(lock)
}
//dosomething...
}
像这种成对调用的方法,可以用defer把它们放在一起,一目了然。
调completionblock
这是一个让我感觉“如果当时知道defer”就好了的场景,就是有时候一个函数分支比较多,可能某个小分支return之前就忘了调completionblock,结果藏下一个不易发现的bug。用defer就可以不用担心这个问题了:
funcfoo(completion:()->Void){
defer{
self.isLoading=false
completion()
}
guarderror==nilelse{return}
//handlesuccess
}
有时候completion要根据情况传不同的参数,这时defer就不好使了。不过如果completionblock被存下来了,我们还是可以用它来确保执行后能释放:
funcfoo(){
defer{
self.completion=nil
}
if(succeed){
self.completion(.success(result))
}else{
self.completion(.error(error))
}
}
调super方法
有时候override一个方法,主要目的是在super方法之前做一些准备工作,比如UICollectionViewLayout的prepare(forCollectionViewUpdates:),那么我们就可以把调用super的部分放在defer里:
funcoverridefoo(){
defer{
super.foo()
}
//somepreparationbeforesuper.foo()...
}
一些细节
任意scope都可以有defer
虽然大部分的使用场景是在函数里,不过理论上任何一个{}之间都是可以写defer的。比如一个普通的循环:
varsumOfOdd=0
foriin0...10{
defer{
print("Look!It's\(i)")
}
ifi%2==0{
continue
}
sumOfOdd+=i
}
continue或者break都不会妨碍defer的执行。甚至一个平白无故的closure里也可以写defer:
{
defer{print("bye!")}
print("hello!")
}
就是这样没什么意义就是了……
必须执行到defer才会触发
假设有这样一个问题:一个scope里的defer能保证一定会执行吗?答案是否……比如下面这个例子:
funcfoo()throws{
do{
throwNSError()
print("impossible")
}
defer{
print("finally")
}
}
try?foo()
不会执行defer,不会print任何东西。这个故事告诉我们,至少要执行到defer这一行,它才保证后面会触发。同样道理,提前return也是一样不行的:
funcfoo(){
guardfalseelse{return}
defer{
print("finally")
}
}
多个defer
一个scope可以有多个defer,顺序是像栈一样倒着执行的:每遇到一个defer就像压进一个栈里,到scope结束的时候,后进栈的先执行。如下面的代码,会按1、2、3、4、5、6的顺序print出来。
funcfoo(){
print("1")
defer{
print("6")
}
print("2")
defer{
print("5")
}
print("3")
defer{
print("4")
}
}
但是我强烈建议不要这么写。我是建议一个scope里不要有多个defer,感觉除了让读代码的人感觉混乱之外没有什么好处。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。