Wednesday, August 10, 2016

Swift defer keyword Tutorial

Let's see the Apple forums description of defer keyword.


     Use 
defer to write a block of code that is executed after all other code 

     in the function, just before the function returns. The code is executed 
     regardless of whether the function throws an error. You can use defer 
     to write setup and cleanup code next to each other, even though they 
     need to be executed at different times.


As per above description, defer block of code is very useful incase If our function ends with an exception without returning anything. Let me discuss this with a real time example by taking an use case. 

In my application, users can do their transaction from their wallet but only 2 free wallet transactions are available per a day. So, every time user tries with his/her wallet I need to update my counter.


   
var availableMoneyInUserWallet = 200
   var transactionTriedWithWallet = 0;

   func payFromWallet(amount: Intthrows ->  Bool {    
    
        transactionTriedWithWallet += 1    
        return true    
   }
    
   let response = try payFromWallet(100//true
   transactionTriedWithWallet  //  1



I have a payFromWallet function which will get called and increments the wallet counter. But, we need to check the available funds in user wallet also before proceeding for payment. If user is  having 0 balance or insufficient funds, We  do need to throw an error.



   
enum WalletError: ErrorType {
    
        case NO_FUNDS
    
        case INSUFFICIENT_FUNDS
    
   }


Above are error types that may happen when accessing user's wallet for payment.


   
func payFromWallet(amount: Intthrows ->  Bool {
    
        if availableMoneyInUserWallet == 0 {
        
             throw WalletError.NO_FUNDS
         }
    
        if availableMoneyInUserWallet < amount {
        
              throw WalletError.INSUFFICIENT_FUNDS
        }
    
        transactionTriedWithWallet += 1
    
        return true
    
   }


I have added piece of code for balance checking and throwing error.  Let's see what happens If I call like below.


  do {
    
      let response = try payFromWallet(300)
      transactionTriedWithWallet
    
  }
    
  catch WalletError.NO_FUNDS{
    
      print("0 Balance!!!")
      transactionTriedWithWallet
    
  }

  catch WalletError.INSUFFICIENT_FUNDS{
    
      print("Insufficient balance for transaction!!!")     // Insufficient balance for transaction!!!                     
      transactionTriedWithWallet     //0
    
  }


It goes to WalletError.INSUFFICIENT_FUNDS catch block and giving transactionTriedWithWallet values as 0 though we are incrementing the counter. This is because the function has thrown an error and increment logic didn't get executed.

Technically, We need that counter to be incremented even there is an error thrown. Here comes the defer block which will get executed just before the function return and the beauty of is, It will get executed even If a function throws an error.



   
func payFromWallet(amount: Intthrows ->  Bool {    

      defer{

          transactionTriedWithWallet += 1 
      }
    
      if availableMoneyInUserWallet == 0 {
          throw WalletError.NO_FUNDS
      }
    
      if availableMoneyInUserWallet < amount {
          throw WalletError.INSUFFICIENT_FUNDS
      }
    
      return true
    
  }


Now call the same function again and see the results.


  do {
    
      let response = try payFromWallet(300)
      transactionTriedWithWallet
  }
    
  catch WalletError.NO_FUNDS{
    
      print("0 Balance!!!")
      transactionTriedWithWallet
  }

  catch WalletError.INSUFFICIENT_FUNDS{
    
      print("Insufficient balance for transaction!!!")      // Insufficient balance for transaction!!!
      transactionTriedWithWallet  //1 
  }


The below code makes more sense and best use case for defer.


   
func payFromWallet(amount: Intthrows ->  Bool {
    
      var isTrasactionSuccessful:Bool = true

      defer{
        
            if isTrasactionSuccessful {
                
                transactionTriedWithWallet += 1
            }
            
            else{
                
                transactionTriedWithWallet  -= 1
            }
        
      }
    
      if availableMoneyInUserWallet == 0 {
          isTrasactionSuccessful = false
          throw WalletError.NO_FUNDS
      }
    
      if availableMoneyInUserWallet < amount {
          isTrasactionSuccessful = false
          throw WalletError.INSUFFICIENT_FUNDS
      }
    
      return isTrasactionSuccessful
    
  }


Hope this post is useful. Feel free to comment in case of any queries.