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,感觉除了让读代码的人感觉混乱之外没有什么好处。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。