Previous Lecture Complete and continue  

  Lecture 16. Log Out and Auto Sign In.

Download

Homework to Make Sense of The Lecture and Dig Deeper

Exercise 1.

do-try

Around 1:15 in the lecture, we used the signOut() method of Firebase to log users out. This method is declared to throw an error.

The objective of this exercise is to understand functions that can throw errors.

(a) Comment everything in the logout_TouchUpInside() method.

(b) We’ll simulate a method of the Sign Up View Controller that can throw an error:

func functionWhichMightFail() throws {            
}
	

We’ll actually make it always throw an error:

func functionWhichMightFail() throws {            
  throw NSError.init(domain: "Some Domain", code: 500, userInfo: nil)
}
	

Then call it the logout_TouchUpInside() method:

@IBAction func logout_TouchUpInside(_ sender: Any) {
    functionWhichMightFail()    
}
	

Now run the app and test the Log Out button. What do you see?

(c) As this method might throw an error, we need to a the try keyword before the function call. Do this work this time?
@IBAction func logout_TouchUpInside(_ sender: Any) {
  try functionWhichMightFail()
}
	

(d) It turns out that this function call should be used in a do block.

@IBAction func logout_TouchUpInside(_ sender: Any) {
  do {
    try functionWhichMightFail()
  }
}
	

What error you see this time?

(e) Our method might throw an error, but we haven’t done anything to handle that error. Therefore, we need to catch that error and handle it properly.

@IBAction func logout_TouchUpInside(_ sender: Any) {
  do {
    try functionWhichMightFail()    
  } catch {
  }
}
	

(f) The app runs smoothly now. What is happening here? Where is the error? Turns out we can catch that error, but we never actually explicitly touch it. To see how it looks like, let’s put it into something and print it out.

@IBAction func logout_TouchUpInside(_ sender: Any) {
  do {
    try functionWhichMightFail()    
  } catch logoutError {
    print("Here is the error. Go handle it!")
    // YOUR CODE HERE
  }
}
	
How does logoutError look like?

(g) Now we can see that the logoutError is actually what ever the functionWhichMightFail() thrown. To see when we actually catch this error, let’s print something out before and after the calling of the functionWhichMightFail():

@IBAction func logout_TouchUpInside(_ sender: Any) {
  do {
    // YOUR CODE HERE
    try functionWhichMightFail()         
    // YOUR CODE HERE
  } catch let logoutError {
    print("Here is the error. Go handle it!")
    print(logoutError)
  }
}
		
What do you observe? Was the print() command after the try functionWhichMightFail() line executed?

Exercise 2.

This exercise is built on top of the previous one. After Exercise 1, we know that we failed to sign users out. Recall that in the lecture, we dismiss the tab bar view outside the do-catch block. Let’s mimic that situation here:

@IBAction func logout_TouchUpInside(_ sender: Any) {
  do {
    print("This should be executed.")
    try functionWhichMightFail()         
    print("This will be executed if things went well.")
} catch let logoutError {
    print("Here is the error. Go handle it!")
    print(logoutError)
  }
  dismiss(animated: true, completion: nil)
}
		

(a) Basically, we pretended that the sign-out process failed. We then dismiss the tab bar view to switch back to the sign-in view. What happens after the app switched back to the sign-in view?

(b) Turns out that because the sign-out process failed, the user is still authenticated. Therefore, the app will automatically switched back to the tab bar controller again, thanks to our Auto Sign-In feature. However, does this desirable? If not, how do you fix it? Hint: re-locate the dismiss() method. Exercise 1.g might be useful here.

Exercise 3.

Unauthorized users

When we logged a user out, one can say that user was unauthenticated. Look for a property of the FIRAuth class and print it out using the shared instance FIRAuth.auth() to support that argument.

Exercise 4

Closure arguments

Closures can be used as parameters of a function. This is useful if you want to pass a closure performing some tasks into the function. This exercise helps you get used to this helpful usage of closures. Below is an instance method of the Sign In View Controller which takes a closure as its only argument.

func methodWithClosureArgument(closure: () -> Void) {
  closure()
}
		

For simplicity, this closure has no input and returns nothing, hence closure: () -> Void. Note that we call this closure inside the method. In the viewDidLoad() method of the Sign In View Controller, call this method. For simplicity, the input closure simply prints something out.

Exercise 5

@escaping vs @noescape closures

In Swift 3, by default, argument closures are @noescape, meaning that it vanishes after the method (which takes it as an input) returned. However, in many cases, it’s useful to use @escaping argument closures, which can be called after the method returned. We’ll encounter such situations later. For now, let’s play around with a toy example to grasp the basic idea.

Recall that in Exercise 4, we created a method of the Sign In View Controller whose only argument is a closure. By default, this is a @noescape closure argument, meaning that it can’t be called after the methodWithClosureArgument() method is called. The purpose of this exercise is to try to call this closure after the method returned. To do so, let’s create an instance variable of the Sign In View Controller which is a closure of same type as our argument closure.

var closureInstance: (() -> Void)!
		

Note that we need to declare this property as an Optional as our Sign In View Controller has no initializer.

(a) In the body of methodWithClosureArgument(), assign the closure argument, instead of calling it, to this new property.

func methodWithClosureArgument(closure: () -> Void) {
  // YOUR CODE HERE
}
		

We might get an error. What does this error say?

(b) It turns out that as the closureInstance closure can potentially be called outside the methodWithClosureArgument() method, whereas that is not allowed for the closure argument closure, Xcode isn’t happy with that. To fix this issue, Xcode requires us to explicitly specify that the closure argument can be called after the method returned. To do so, we can simply use the @escaping keyword.

func methodWithClosureArgument(closure: @escaping () -> Void) {
  closureInstance = closure
}
		

Note that this @escaping characteristic of the closure argument here is required even if it is only potentially called outside the method: we haven’t tried to called it after the method methodWithClosureArgument() returned.

(c) In the viewDidLoad() method of the Sign In View Controller, call the closureInstance closure after the methodWithClosureArgument() is called.

methodWithClosureArgument(closure: {
  print("Method with a closure argument got called.")
})
  // YOUR CODE HERE
		

Before running the app, print something out at the end of the methodWithClosureArgument() method to signal when it returns.

func methodWithClosureArgument(closure: @escaping () -> Void) {
  closureInstance = closure
  // YOUR CODE HERE
}
		

What do you observe now? Describe an @escaping closure in one sentence: “An escaping closure is a closure that…”

(d) Does this remind you of Firebase methods we've been working with?

Discussion
10 comments