07/09/2018, 09:55

Swift: Optional và Thuật ngữ liên quan tới Optional

Mình là một dev đi lên từ Objective-C, ban đầu khi tìm hiểu về Swift mình đã gặp phải vấn đề kha khá về Optional (trong Objective-C đơn giản là chỉ cần check nil object != nil). Khi mới code chắc hẳn không ít người gặp vấn đề với những warning hay error về Optional, và thật tiện là Xcode cung ...

Mình là một dev đi lên từ Objective-C, ban đầu khi tìm hiểu về Swift mình đã gặp phải vấn đề kha khá về Optional (trong Objective-C đơn giản là chỉ cần check nil object != nil). Khi mới code chắc hẳn không ít người gặp vấn đề với những warning hay error về Optional, và thật tiện là Xcode cung cấp một nút fix thật đơn giản (just click). Nhưng sau khi compile thì chúng ta sẽ có thể gặp cái lỗi muôn thuở fatal error: unexpectedly found nil while unwrapping an Optional value

Tại sao lại nil được? Unwarpping nghĩa là gì?

Việc chúng ta không hiểu được bản chất về Optional và chỉ bấm nút fix mà không hiểu tại sao sẽ rất hay dẫn tới những lỗi như vậy khi sử dụng Optional. Vì vậy bài viết lần này của mình sẽ là nghiên cứu về Optional và các thuật ngữ về nó.

Tại sao lại sử dụng Optional? Chỉ là check nil thôi mà?

Optional được giới thiệu với mục đích phục vụ cho nil. Nil ở đây là không có giá trị, hay chả có gì trong nó cả. Một biến được gán giá trị là nil có nghĩa là nó không có dữ liệu, một biến được định nghĩa là kiểu optional có thể sử dụng nil để thể hiện rằng biến đó không có dữ liệu.

Vậy tại sao chúng ta phải để giá trị nil cho một biến? Trong thực tế có rất nhiều trường hợp một biến có thể có giá trị hoặc không. Như dữ liệu được trả về từ server chúng ta không thể đảm bảo rằng biến chúng ta cần từ server trả về lúc nào cũng có dữ liệu, hay đơn giản một ví dụ về tên của người.

struct Person {
    let firstName: String
    let middleName: String
    let lastName: String
}

Với ví dụ trên chúng ta đặc mặc nhiên là một người mà tên của họ lúc nào cũng có Họ, Tên đệm và Tên. Nhưng trong rất nhiều trường hợp một người nào đó không có tên đệm.

Chúng ta có thể sửa lỗi này bằng các thay nil bằng một chuỗi rỗng "" - Điều này thể hiện là tên đệm của họ là một chuỗi rỗng, hay biến middleName có giá trị và giá trị của nó là "". Trong khi đó thực tế thì họ không có tên đệm. Để biểu diễn đúng chúng ta nên để kiểu của middleName là một optional.

struct Person {
    let firstName: String
    // make it optional, in case a person doesn't have a middle name
    let middleName: String?
    let lastName: String
}

Như vậy giờ đoạn code sẽ có nghĩa là một người sẽ có Họ và Tên, và Tên đệm của họ có thể có hoặc không. Lúc này chúng ta cũng có thể gán cho middleName giá trị nil mà không bị báo lỗi. Nhưng vẫn còn vấn đề ở đây, khi chúng ta lấy một biến là optional để sử dụng, sẽ gặp những vấn đề như vậy

Hay cả khi chúng ta đã gán cho nó một giá trị khác nil.

Mặc dù đã gán một giá trị cụ thể nhưng tại sao lại kết quả lại vẫn vậy? Thực chất khi chúng ta định nghĩa một biến kiểu optional thì giá trị của nó đã được đóng gói, bao bọc (wrapped) bên trong Optional enum. Vì vậy muốn sử dụng các biến này chúng ta cần unwrap.

Force unwrap

Cách đầu tiên và đơn giản nhất là Force unwrap, đơn giản là chỉ cần đặt ! sau biến mà chúng ta đang cần sử dụng. Rất đơn giản, rất dễ dàng và đặc biệt là rất nguy hiểm. Bởi vì như đã nói khi một biến được định nghĩa là Optional có nghĩa là nó có thể nil, và khi sử dụng Force unwrap và biến đó bị nil sẽ dẫn đến app của chúng ta sẽ CRASH

Vì vậy, khi sử dụng Force unwrap thì hãy nhớ rằng: "Chỉ sử dụng Force Unwrap nếu bạn chắc chắn 100% biến đó sẽ không nil"

Optional binding

Vậy nếu bạn không chắc chắn biến mà bạn cần sử dụng luôn có giá trị và bạn vẫn muốn sử dụng nó thì sao? Đơn giản thì chúng ta có thể kiểm tra nil hay không và gán vào một biến non-optional để sử dụng. Việc cách làm này trở nên thông dụng và Swift đa cung cấp cho chúng ta shortcut để unwrap gọi là Optional binding bằng việc sử dụng if let hay guard let.

Swift cũng cho phép việc trùng tên đối với optional binding if let. Với optional binding if let thì các biến sẽ được kiểm tra nil hay không và nếu không nil sẽ được unwrap và thực thi đoạn code trong block của if let. Optional binding guard let thì khác ở điểm sau khi check nil nếu nil thì sẽ thực hiện code ở trong block else {} còn nếu không thì biến cũng được unwrap và sử dụng ở dưới.

Optional chaining

Việc check nil với Optional binding thực hiện rất đơn giản và thuận tiện, nhưng nếu kịch bản bây giờ chúng ta muốn thực hiện một function nào đó nếu mà đối tượng không nil thì sao? Như với kịch bản ở dưới.

struct Person {
    let firstName: String
    let middleName: String?
    let lastName: String
    let pet: Pet?
	
    func eat(){
        print("(firstName) nom nom")
    }
}

struct Pet {
    func makeNoise(){
        print("woof")
    }
}

Nếu chúng ta muốn thực hiện một kịch bản rằng nếu có một ai đó và nếu họ nuôi pet thì hãy thực hiện function makeNoise(). Với Optional binding ở trên thì chúng ta sẽ if let hay guard let để đảm bảo rằng có một người nào đó, và họ có nuối pet sau đó mới thực hiện function makeNoise(). Việc này có thể thực hiện một cách dễ ràng và ngắn gọn hơn với Optional chaining:

let vulpes : Pet? = Pet()
let axel : Person? = Person(firstName: "Axel", middleName: nil, lastName: "Kee", pet: vulpes)

// makeNoise will only execute if axel is not nil and axel's pet is not nil
axel?.pet?.makeNoise()

//output 'woof'

Optional chaining được thực hiện bằng cách đặt dấu ? sau biến và khi thực thi IDE sẽ hiểu rằng nếu biến đó không nil thì sẽ tiếp tục thực thi sang bên phải, và nếu bị nil thì việc thực thi sẽ bị dừng lại và thực thi các dòng lệnh ở bên dưới.

Nil coalescing

Với trường hợp bạn muốn có một giá trị mặc định cho những biến Optional nếu nó bị nil thì Nil coalescing sẽ giúp bạn làm điều nãy.

var sides : String? = nil

// if sides is nil, French Fries will be used
let confirmedSides : String = sides ?? "French Fries"
print("Meal with (confirmedSides)")

// output 'Meal with French Fries'

Dấu ?? chính là Nil coalescing operator. Nó định nghĩa biến đằng sau nó (với trường hợp trên là "French Fries") sẽ được sử dụng nếu như biến đằng trước nó (với trường hợp trên là sides) nil.

Summary

Chúng ta không thể tránh khỏi sự tồn tại của Nil trong code vì vậy hãy xử lý nó. Và Optional là giúp chúng ta xử lý những vấn đề này dễ dàng hơn, an toàn hơn. Mong là bài viết đã giúp các bạn hiểu được cơ bản về Optional và các thuật ngữ xung quanh nó.

Reference: https://fluffy.es/eli-5-optional/#optional-chaining

0