Brief comment:
A function is a collection of statements organized together to perform specific tasks. Swift functions are similar to simple C functions and complex Objective C functions. It enables us to call local and global parameter values inside a function call. Swift functions follow the same steps as any other language.
- Function declaration: It tells the compiler the name, return type, and parameters of the function related to.
- Function definition: It provides the actual body of the function.
Swift functions contain parameter types and return types.
The main benefit of function return in advance is that each error handling is separated, and there is no need to consider multiple complex exceptions when reviewing the code. We can focus on business logic and break points directly in exceptions when debugging the code.
Return in advance
First, let’s take a look at the code examples that need to be improved. We build a note application using the NotificationCenter API. When the notes content changes, Notification notifies the note list to change. The code is as follows:
class NoteListViewController: UIViewController { @objc func handleChangeNotification(_ notification: Notification) { let noteInfo = ?["note"] as? [String : Any] if let id = noteInfo?["id"] as? Int { if let note = (withID: id) { notes[id] = note () } } } }
The above code works well, but it is a bit less readable. Because this code contains multiple indentation and type conversion. Let's try to improve this code.
- Return the method in advance, so that our function returns as fast as possible.
- Use guard instead of if to avoid nesting.
class NoteListViewController: UIViewController { @objc func handleChangeNotification(_ notification: Notification) { let noteInfo = ?["note"] as? [String : Any] guard let id = noteInfo?["id"] as? Int else { return } guard let note = (withID: id) else { return } notes[id] = note () } }
Returning a function early can handle functionality failures more clearly, which not only improves readability (less indentation, less nesting), but also benefits unit testing.
We can further improve the code and place the code that gets the noteID and type conversion in the Notification Extension, thus separating the handleChangeNotification business logic and specific details. The modified code looks like this:
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 = else { return } guard let note = (withID: id) else { return } notes[id] = note () } }
This structure also greatly simplifies the difficulty of debugging. We can directly add breakpoints to return in each guard to intercept all failures without stepping through all logic.
Conditional structure
When constructing an object instance, it is very common to construct which type of object needs to be built depends on a series of conditions.
For example, which view controller is displayed when starting an application depends on:
- Whether you are already logged in.
- Whether the user has completed the onboarding flow.
Our implementation of these conditions may be a series of if and else statements, as shown below:
func showInitialViewController() { if { if { = [HomeViewController()] } else { = [OnboardingViewController()] } } else { = [LoginViewController()] } }
The same early return and guard statement can improve code readability, but now this situation is not a failure situation, but a different view controller is built under different conditions.
Now to improve this code, using a lightweight engineering pattern, move the initial construction interface into a special function that returns the view controller that matches the condition. As shown below:
func makeInitialViewController() -> UIViewController { guard else { return LoginViewController() } guard else { return OnboardingViewController() } return HomeViewController() } func showInitialViewController() { let viewController = makeInitialViewController() = [viewController] }
Since the makeInitialViewController method is a pure function (it does not affect the external state, fixed input can obtain fixed output), there is actually only one place that affects the external state = [viewController] (In daily development, if the state is not well controlled, it is easy to cause bugs, so using less state and reducing the modification of the state can reduce the chance of bugs to a certain extent).
Conditional control
Finally, let’s take a look at how functions simplify complex conditional logic. Let's build a view controller to display the comment function of social applications. If three conditions are met, run the user to edit the comment. The code is as follows:
class CommentViewController: UIViewController { override func viewDidLoad() { () if == { if { if ! { let editButton = UIButton() ... (editButton) } } } ... } }
3 if nested logic is used here, and it will be more troublesome every time you re-examine the code. With more previous experience, we can optimize the code and add Comment extension:
extension Comment { func canBeEdited(by user: User) -> Bool { guard authorID == else { return false } guard else { return false } return !edited } } class CommentViewController: UIViewController { override func viewDidLoad() { () if (by: user) { let editButton = UIButton() ... (editButton) } ... } }
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.