The Swift function returns the instance detail ahead of time

  • 2020-06-15 10:21:54
  • OfStack

Profile:

A function is a collection of statements organized at 1 to perform a specific task. The Swift function is similar to the simple C function and the complex Objective C language function. It enables us to call internal local and global parameter values via functions. The swift function follows the same steps as in any other language.

Function declaration: This tells the compiler the name, return type, and arguments of the function concerned. Function definition: This provides the actual body of the function.

The Swift function contains the argument type and the return type.

The main benefit of the early return of the function is that it separates each error handler, doesn't have to worry about multiple complex exceptions when reviewing the code, and allows us to focus on the business logic and debug the code so that we can directly break points in the exception.

Return early

First, let's look at the code example that needs to be improved. We build a note application that USES NotificationCenter API to notify note list changes when note content changes. The code is as follows:


class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
let noteInfo = notification.userInfo?["note"] as? [String : Any]

if let id = noteInfo?["id"] as? Int {
if let note = database.loadNote(withID: id) {
notes[id] = note
tableView.reloadData()
}
}
}
}

The above code works fine, but it's a bit unreadable. Because this code contains multiple indenting and casting. Let's try to improve this code.

Let's return the method earlier, and let's return the function as quickly as possible. Use guard instead of if to avoid nesting.

class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
let noteInfo = notification.userInfo?["note"] as? [String : Any]

guard let id = noteInfo?["id"] as? Int else {
return
}

guard let note = database.loadNote(withID: id) else {
return
}

notes[id] = note
tableView.reloadData()
}
}

Returning a function earlier makes it easier to handle failure cases, which not only improves readability (less indentation, less nesting), but also facilitates unit testing.

We could refine the code one step further and put the code that gets noteID and type conversions in Notification Extension, thus separating the business logic of handleChangeNotification from the details. The modified code is as follows:


private extension Notification {
var noteID: Int? {
let info = userInfo?["note"] as? [String : Any]
return info?["id"] as? Int
}
}

class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
guard let id = notification.noteID else {
return
}

guard let note = database.loadNote(withID: id) else {
return
}

notes[id] = note
tableView.reloadData()
}
}

This structure also greatly simplifies debugging by allowing you to add breakpoints to return per guard to catch all failures without having to step through all the logic.

Conditions of construction

When constructing an object instance, a very common requirement is that what kind of object needs to be built depends on the 1 series of conditions.

For example, which view controller is displayed when the application is launched depends on:

Whether or not you are logged in. Whether the user has completed the onboarding process (onboarding flow).

Our implementation of these conditions might be the if and else statements of series 1, as shown below:


func showInitialViewController() {
if loginManager.isUserLoggedIn {
if tutorialManager.isOnboardingCompleted {
navigationController.viewControllers = [HomeViewController()]
} else {
navigationController.viewControllers = [OnboardingViewController()]
}
} else {
navigationController.viewControllers = [LoginViewController()]
}
}

The same early return and guard statement can improve the readability of the code, but instead of dealing with failure cases, this case builds different view controller under different conditions.

Now to improve on this code, use the lightweight engineering pattern to move the construction initial interface into a specialized function that returns view controller matching the criteria. As shown below:


func makeInitialViewController() -> UIViewController {
guard loginManager.isUserLoggedIn else {
return LoginViewController()
}

guard tutorialManager.isOnboardingCompleted else {
return OnboardingViewController()
}

return HomeViewController()
}

func showInitialViewController() {
let viewController = makeInitialViewController()
navigationController.viewControllers = [viewController]
}

Because the makeInitialViewController method is a pure function (does not affect the external condition, fixed input can be fixed output), actually affect the external state of only 1 place navigationController. viewControllers = [viewController], (in the day-to-day development state if not well control is easy to cause bug, so use less status, and to reduce state changes can reduce the chances of getting bug 1 set).

Under controlled conditions

Finally, let's look at how functions simplify complex conditional logic. Let's build an view controller to display the comment function of the social application and run the user to edit the comment if three conditions are met. The code is as follows:


class CommentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

if comment.authorID == user.id {
if comment.replies.isEmpty {
if !comment.edited {
let editButton = UIButton()
...
view.addSubview(editButton)
}
}
}

...
}
}

Three if nested logic are used here. Each time we review the code, it will be a bit confusing. Based on our previous experience, we can optimize the code and add Comment extension:


extension Comment {
func canBeEdited(by user: User) -> Bool {
guard authorID == user.id else {
return false
}

guard comment.replies.isEmpty else {
return false
}

return !edited
}
}

class CommentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

if comment.canBeEdited(by: user) {
let editButton = UIButton()
...
view.addSubview(editButton)
}

...
}
}

conclusion


Related articles: