SoFunction
Updated on 2025-04-11

Detailed explanation of the non-optional optional value type processing method in Swift

Preface

When we use objective-c to represent string information, we can write it in the following method.

NSString *str = @"Autumn hatred of snow"; 
str = nil; 

Because objective-c is a weak-type language, str can be either a specific string or nil. But it is not possible in Swift, because Swift is a type-safe language. A variable of type String cannot be both a specific string or a nil (more strictly speaking, the content of type String can only be a string). So, there is the concept of optional types in Swift. (In fact, this concept is also "borrowed" from other programming languages, such as C#, but it is called the nullable type in C#).

When you first saw Optionals, I was very unfamiliar with it. When I first saw it, I was thinking... What the hell is this... But if you think about it carefully, the introduction of optional value Optionals has also brought us convenience.

Optionals are indisputable, one of the most important features of the swift language, and the biggest difference from other languages ​​such as Objective-C. By forcing the possibility of nil, we can write more predictive and robust code.

However, sometimes optional values ​​can cause you to embarrassingly, especially as you as a developer understand (even some guessing components in), some specific variables are always non-nil, even if it is an optional type. For example, when we process a view in a view controller:

class TableViewController: UIViewController {
 var tableView: UITableView?
 override func viewDidLoad() {
  ()
  tableView = UITableView(frame: )
  (tableView!)
 }
 func viewModelDidUpdate(_ viewModel: ViewModel) {
  tableView?.reloadData()
 }
}

This is also a place where many Swift programmers are more debated, and it is no less than discussing the usage of tabs and spaces. Some people will say:

Since it is an optional value, you should always use if let or guard let to unpack.

However, others use the exact opposite, saying:

Since you know that this variable will not be nil when used, use ! How good is it to force unpack. It's better to crash than to leave your program in an unknown state.

Essentially, what we are talking about here is whether to use defensive programming (defensive programming) problem. Are we trying to get the program to recover from an unknown state or simply give up and let it crash?

If I had to give an answer to this question, I would prefer the latter. Unknown states are really difficult to track. Bugs will cause a lot of logic that you don't want to execute. Defensive programming will make it difficult for your code to track and problems will be difficult to track.

However, I don't really like to give an answer that goes either way. Instead, we can find some technical methods to solve the problems mentioned above in a more sophisticated way.

Is it really optional?

Those variables and attributes that are optional, but are actually required by the code logic, are actually a reflection of architectural flaws. If it is really needed in some places, but it is not there, it will leave your code logic in an unknown state, then it should not be of an optional type.

Of course, in certain specific scenarios, optional values ​​are indeed difficult to avoid (especially when interacting with specific system APIs), so for most of this situation, we have some techniques to deal with to avoid optional values.

lazy is better than non-optional optional values

The values ​​of certain properties need to be generated after their parent class is created (for example, those views in the view controller should be created in the loadView() or viewDidLoad() methods). For this property, the method to avoid optional types is to use the lazy attribute. A lazy attribute can be of a non-optional type and is not required in the initialization method of its parent class. It will be created when it is first retrieved.

Let's change the above code and use lazy to modify the tableView property:

class TableViewController: UIViewController {
 lazy var tableView = UITableView()
 override func viewDidLoad() {
  ()
   = 
  (tableView)
 }
 func viewModelDidUpdate(_ viewModel: ViewModel) {
  ()
 }
}

In this way, there is no optional value and there will be no unknown state.

Proper dependency management is better than non-optional optional values

Another commonly used scenario is to break circular dependencies (circular dependencies). Sometimes, you get stuck in the situation where A depends on B, and B depends on A, as follows:

class UserManager {
 private weak var commentManager: CommentManager?
 func userDidPostComment(_ comment: Comment) {
   += 1
 }
 func logOutCurrentUser() {
  ()
  commentManager?.clearCache()
 }
}
class CommentManager {
 private weak var userManager: UserManager?
 func composer(_ composer: CommentComposer
     didPostComment comment: Comment) {
  userManager?.userDidPostComment(comment)
  handle(comment)
 }
 func clearCache() {
  ()
 }
}

From the above code, we can see that there is a circular dependency problem between UserManager and CommentManager. Neither of them can assume that they have each other, but they both depend on each other in their respective code logic. It is easy to cause bugs here.

To solve the above problem, we create a CommentComposer to be a coordinator, responsible for notifying the UserManager and CommentManager that a comment was generated.

class CommentComposer {
 private let commentManager: CommentManager
 private let userManager: UserManager
 private lazy var textView = UITextView()
 init(commentManager: CommentManager,
   userManager: UserManager) {
   = commentManager
   = userManager
 }
 func postComment() {
  let comment = Comment(text: )
  (comment)
  (comment)
 }
}

In this form, UserManager can forcefully hold CommentManager and do not generate any dependency loops.

class UserManager {
 private let commentManager: CommentManager
 init(commentManager: CommentManager) {
   = commentManager
 }
 func userDidPostComment(_ comment: Comment) {
   += 1
 }
}

We have removed all optional types again, and the code is better predictable.

Crashing gracefully

Through the above examples, we eliminate uncertainty by making some adjustments to the code and removing optional types. Sometimes, however, it is impossible to remove optional types. Let's give an example. For example, if you are loading a local JSON file containing configuration items for your App, the operation itself will definitely fail, so we need to add error handling.

Continue with the above scenario, continuing to execute code when loading the configuration file fails to cause your app to enter an unknown state. In this case, the best way to crash it. In this way, we will get a crash log, hoping that this problem can be processed by our testers and QA early before the user perception.

So, how do we collapse. . . The easiest way is to add the ! operator, which forces unpacking for this optional value, and will crash when it is nil:

let configuration = loadConfiguration()!

Although this method is relatively simple, it has a big problem, that is, once this code crashes, all we can get is an error message:

fatal error: unexpectedly found nil while unwrapping an Optional value

This error message does not tell us why this error occurred, where it happened, and cannot give us any clues to solve it. At this time, we can use the guard keyword, combine the preconditionFailure() function, and give a customized message when the program exits.

guard let configuration = loadConfiguration() else {
 preconditionFailure("Configuration couldn't be loaded. " +
      "Verify that  is valid.")
}

When the above code crashes, we can get more and more effective error messages:

fatal error: Configuration couldn't be loaded. Verify that  is valid.: file /Users/John/AmazingApp/Sources/, line 17

In this way, we now have a clearer solution to the problem and can accurately know which unknown occurred in our code.

Introducing the Require library

The solution using the guard-let-preconditionFailure above is still a bit verbose, which really makes our code more difficult to control. We really don't want to occupy a lot of space in our code to take up some of this kind of code. We want to focus more on our code logic.

My solution is to use Require. It simply adds a simple require() method to optional values, but can make the call simpler. Use this method to process the code loading the JSON file above and you can write it like this:

let configuration = loadConfiguration().require(hint: "Verify that  is valid")

When an exception occurs, the following error message will be given:

fatal error: Required value was nil. Debugging hint: Verify that  is valid: file /Users/John/AmazingApp/Sources/, line 17

Another advantage of Require is that it will throw an exception NSException just like calling the preconditionFailure() method, which enables those exception reporting tools to capture the metadata when the exception occurs.

If you want to use it in your own code,Require is now open source on Github

Summarize

So, in summary, I have a few thoughtful tips for you to deal with non-optional optional values ​​in the Swift language:

  • The lazy attribute is better than non-optional optional values
  • Proper dependency management is better than non-optional optional values
  • Elegant crash when you use non-optional optional values

Okay, the above is the entire content of this article. I hope that the content of this article has a 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.