private func resetAfterError() throws
{
defer
{
selectedIndex = 0
selectedIndex = 0
isError = false
}
if /* condition */
{
// Do stuff
return
}
if /* other condition */
{
// Do other stuff
return
}
// Do default stuff
{
// Do stuff
return
}
if /* other condition */
{
// Do other stuff
return
}
// Do default stuff
}
In my usage to date there has always been some code that should always be executed prior to the function's exit and additionally only one piece of code. Therefore I've always put the defer statement at the top of the function so when reading it's pretty obvious.
I was aware that if there were multiple defer statements then they'd be executed in reverse order but what I'd not given any thought to before was what happens if the defer statement isn't reached. In fact I'd just assumed it was more of a declaration that this code should always be executed on function exit and as I put mine right at the start of the function this was effectively the case.
However, for some functions (probably most) you don't want this. You only want the deferred code executing if some else as happened. This is shown simply in The Swift Programming Language book example:
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// Work with the file.
}
// close(file) is called here, at the end of the scope.
}
}
In this if the file is not opened then the deferred code should not be executed. Another very important usage is:
extension NSLock
{
func synchronized<T>(@noescape closure: () throws -> T) rethrows -> T
{
{
self.lock()
defer
{
self.unlock()
}
}
return try closure()
}
}
If the lock is never obtained then it should never be unlocked. In this case this shouldn't have as the self.lock() will not return until it obtains the lock but if that line were replaced with self.
This is how defer works. If the defer statement is never reached and/or encountered then the deferred code block will never be executed. This includes branches (if-statements etc.). The following example:
enum WhenToReturn
{
case After0
case After1
case After2
}
func deferTest(whenToReturn: WhenToReturn, shouldBranch: Bool)
{
print("Defer Test - whenToReturn:\(whenToReturn), shouldBranch:\(shouldBranch)")
defer
{
print("defer 0")
}
print("0")
if whenToReturn == WhenToReturn.After0
{
return
}
defer
{
print("defer 1")
}
print("1")
if whenToReturn == WhenToReturn.After1
{
return
}
if shouldBranch
{
defer
{
print("shouldBranch")
}
}
defer
{
print("defer 2")
}
print("3")
}
deferTest(WhenToReturn.After0, shouldBranch: false)
deferTest(WhenToReturn.After1, shouldBranch: true)
deferTest(WhenToReturn.After2, shouldBranch: false)
deferTest(WhenToReturn.After2, shouldBranch: true)
Results:
Defer Test - whenToReturn:After0, shouldBranch:false
0
defer 0
Defer Test - whenToReturn:After1, shouldBranch:true
0
1
defer 1
defer 0
Defer Test - whenToReturn:After2, shouldBranch:false
0
1
3
defer 2
defer 1
defer 0
Defer Test - whenToReturn:After2, shouldBranch:true
0
1
shouldBranch
3
defer 2
defer 1
defer 0
Program ended with exit code: 0
try
{
// Try some stuff
}
finally
{
// Always do something having tried something regardless of whether it worked or not
}
Whereas the only and actual context of the defer block is it's position.
No comments:
Post a Comment