defer in Swift

defer is used to define the set of statements that should be executed just before the control leaves the current scope of execution.
The statements defined in defer block are executed irrespective of how the execution control leaves the scope in which defer is defined.
The statements defined in defer block are executed just before the control leaves the scope of the block in which defer block is defined. Conversely saying the statements in defer block do not execute immediately.
You would generally use defer to define the cleanup operations like closing any Database connection, freeing up resources, invalidating the login session etc.
The statement consists of defer keyword followed by statement in curly braces.

 
defer {
     statement 1
     statement 2
     .......
}

Lets see an example of using defer.
Consider following code

func countTillHundred() {
    defer {
        print("I am tired")
    }
    
    print("1")
    print("2")
    print("3")
}
countTillHundred()

What is your guess? What would be the sequence of statements. YES! you are right. As expected though I am tired is writtenfirst, but this is written inside defer block. So it is printed just before the function returns.
Screen Shot 2017-07-18 at 10.44.35 PM

Now lets change the definition of the function to following

func countTillHundred() {
    if (1 < 2) {
        defer {
            print("I am tired")
        }
    }
    
    
    print("1")
    print("2")
    print("3")
}
countTillHundred()

This time? This time I am tired will get printed first. Reason being code written inside defer block is deferred only until the block in which it is defined.
Screen Shot 2017-07-18 at 10.55.51 PM

I considered these examples just to show you how and when is code under defer gets executed. Speaking of when the defer block is executed, what do you think would be the order of print statemnts in following code example

func countTillHundred() {
    
    defer {
        print("I am tired")
    }
    defer {
        print("I am really tired")
        
    }
    defer {
        print("I am really really tired")
    }
    print("1")
    print("2")
    print("3")
}
countTillHundred()

The deferred statements defined in single scope are executed in the reverse order of their definition just before the execution leaves the scope. The defer defined last gets executed first. So the output would be
Screen Shot 2017-07-18 at 11.04.36 PM

Now lets see how defer can be very useful in some scenarios. Consider following

enum DatabaseError: Error {
    case alreadyOpened
    case outOfMemory
    case invalidOperation
    case insufficientPrivilege
}

enum DBOperation {
    case insert
    case update
    case read
    case delete
}


func openDB(dbName : String) {
    print("\(dbName) opened")
}


func closeDB(dbName : String) {
    print("\(dbName) closed")
}

func performOperation( operation : DBOperation) throws {
    switch operation {
    case .read:
        print("Record read")
    case .delete,.insert,.update :
        throw DatabaseError.insufficientPrivilege
    }
    
}

Above we have some sample template for database operations. Function performOperation throws and error if operation is other than .read.
Lets consider the following

 
func performSequenceOfOperartions() throws {
    let firstDB = "Database1"
    openDB(dbName: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .delete, on: firstDB)
    closeDB(dbName: firstDB)
}

func main() {
    do {
       try performSequenceOfOperartions()
    } catch {
        print("Could not perfrom operations")
    }
}

main()

In the above code we are trying to perform a sequence of operations on Database1. Specifically two reads following by one delete. Then we are closing the the database. Can you identify the problem with the code?
The database is never closed. As the function performSequenceOfOperartions does not return normally. It returns by throwing an error while we try to perform delete operation. Due to which the exection never reaches closeDB(dbName: firstDB). Hence the database remains in open state.
Screen Shot 2017-07-18 at 11.41.54 PM
We can close the database by writing closeDB(dbName: firstDB) in catch block of try statements. But this would make the code hard to read and debug. An elegant way would be to write the closeDB(dbName: firstDB) in defer block just after we open the database. This would make sure that the function may return for whatever reason, but database will be closed before it is returned.

func performSequenceOfOperartions() throws {
    let firstDB = "Database1"
    openDB(dbName: firstDB)
    defer {
      closeDB(dbName: firstDB)
    }
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .delete, on: firstDB)
}

func main() {
    do {
       try performSequenceOfOperartions()
    } catch {
        print("Could not perfrom operations")
    }
}

main()

Now you will notice that the database is successfully closed.
Screen Shot 2017-07-18 at 11.50.48 PM

One thing worth noting is that for defer block to be scheduled for execution, its declaration should be reached before the control leaves the scope. If due to some reason e.g.break,return,throw etc.the control leaves scope even before defer block declaration, the defer block would not be executed. To make things clear consider following example

func performSequenceOfOperartions() throws {
    let firstDB = "Database1"
    openDB(dbName: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .delete, on: firstDB)
    defer {
        closeDB(dbName: firstDB)
    }
}

func main() {
    do {
       try performSequenceOfOperartions()
    } catch {
        print("Could not perfrom operations")
    }
}

main()

In the above example I have put the defer block declaration at the end. But performOperation with delete takes the control out, even before defer block is declared.Therefore in this case defer block would not execute. Screen Shot 2017-07-19 at 10.55.23 AM.png

Ideal place for declaring defer block is just after the successful execution of statement ,for which cleanup is required. e.g Define the defer block to close the database connection just after connection is successfully opened.

One more thing, The deferred statements may not contain any code that would transfer control out of the statements, such as a break or a return statement, or by throwing an error. For example if I try to put return in our example defer block, I will get compile time error.

func performSequenceOfOperartions() throws {
    let firstDB = "Database1"
    openDB(dbName: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .read, on: firstDB)
    try performOperation(operation: .delete, on: firstDB)
    defer {
        closeDB(dbName: firstDB)
        return // Error :  'return' cannot transfer control out of a defer statement
    }
}

I hope you enjoyed learning about defer block. HAPPY LEARNING!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s