Toàn tập về Optional trong Swift
Giới thiệu
Trước khi mình học Swift thì có học qua C++, C#, Ruby, Python, những ngôn ngữ này đều không có kiểu Optional, hoặc có nhưng khó dùng ( hiện giờ C++, C# cũng có optional ). Mình cũng phải mất khoảng thời gian mới hiểu rõ Optional trong Swift. Bạn có tự hỏi khi nào nên xài !, khi nào xài ?, và tại sao có 2 dấu này trong Swift. Rồi mấy thuật ngữ như Forced Unwrapping, Optional Binding, Nil coalescing là cái khỉ gì? Khi vọng bài viết này sẽ giúp bạn hiểu rõ về kiểu Optional này cũng như những thắc mắc trên.
Tại sao cần có kiểu Optional
Trước khi tìm hiểu những khái niệm như Forced Unwrapping, Optional Binding, Implicitly Unwrapped Optionals, Nil coalescing, Optional Chaining là gì. Chúng ta cần hiểu được tại sao cần có Optional.
Một trong những lỗi thường gặp nhất của lập trình viên là lỗi dùng một biến nhưng biến đó bị nil ( null ).
Lúc mình học thì cứ nghĩ: "Ừ, biến mình khởi tạo mà, mắc mớ gì nó bị nil mà mình không biết". Vậy nguyên nhân do đâu? Thực tế thì đâu là những trường hợp mà một biến dễ bị nil nhất:
Do user, ví dụ bắt user nhập username và password nhưng họ lại để trống rồi nhấn nút Login luôn. Nếu dev không check xem user có nhập đúng không sẽ bị lỗi.
Đổi kiểu dữ liệu. Ví dụ nút cần tính tổng tiền cho user nhưng control textfield trả về giá trị luôn luôn là kiểu string, nếu user nhập không phải là số mà dev không check và lấy giá trị sai này convert sang double luôn thì biến cũng nil.
Do lấy dữ liệu từ server. Ví dụ bạn parse JSON từ API, nhưng ông backend lại không trả về dữ liệu cho bạn mà bạn không check lại thì cũng teo.
Khi phát triển ứng dụng thì sẽ có rất nhiều trường hợp khác sẽ dẫn đến biến bị nil, nhưng trên đây là 3 trường hợp hay bị nhất. Đó là lý do có kiểu Optional trong Swift. Các bạn cũng đừng thần thánh quá Swift, vì kiểu Optional trong Swift cũng mượn ý tưởng từ các ngôn ngữ trước như Rust.
Kiểu Optional
Optional cũng như bao kiểu dữ liệu khác trong Swift. Chẳng hạn ta có UIImage và UIImage?. Chúng là 2 kiểu dữ liệu khác nhau. Khi bạn khai báo một biến kiểu UIImage, thì bạn đang nói với complier là "biến kiểu UIImage này của tao chắc chắn sẽ có giá trị, đừng lo". Ví vụ bạn có một biến là appLogo để lưu cái hình logo của app, và bạn gán cho nó một hình luôn.
Còn khi bạn khai báo biến kiểu UIImage?, thì bạn đang nói với complier là "biến này của tao là kiểu UIImage, có thể nó sẽ bị nil đó, khi tao xài biến này nhớ nhắc tao nha". Ví dụ bạn đang làm một app mạng xã hội giống Facebook, bạn đang parse các status từ API. Mà một status thì sẽ có thể có caption hoặc hình hoặc video.
struct Status { var statusId: Int var createdAt: String var userId: Int var caption: String? var image: UIImage? var videoUrl: String? }
Vì caption, hình và videoUrl có thể nil nên bạn khai báo kiểu Option cho chúng.
Thực chất, Optional là một enum
enum Optional<T> { case None case Some(T) }
Do đó, bạn có thể khai báo biến kiểu Optional như vậy cũng được, nhưng thưc tế ít ai dùng cách thứ 2 lắm
// Hai dòng code này như nhau let x: String? = nil let x = Optional<String>.none
// Hai dòng code này như nhau let y: String? = "Hi there" let y = Optional<String>.some("Hi there")
Forced Unwrapping
Khi bạn khai báo một biến kiểu Optional và bạn dùng nó, complier sẽ thông báo và nhắc cho bạn biết là "mày đang dùng kiểu Optional kìa"
Khi bạn thêm dấu ! vào sau một biến kiểu Optional thì bạn đang nói với complier là: "Vô tư đi, không sao đâu, tui biết chắc chắc biến đó có giá trị mà, không nil đâu mà sợ"
Nhưng để an tâm, bạn có thể check xem biến yearOfBirth có giá trị không rồi thêm ! ( forced unwrapping ) cũng được:
var yearOfBirth: Int? if yearOfBirth != nil { var age = 2018 - yearOfBirth! }
Mà tại sao lại gọi là forced unwrapping. Bạn cứ tưởng tượng giá trị của biến kiểu Optional đang được bỏ trong một cái hộp ( box ). Bạn muốn lấy phải mở hộp ra ( unwrapping ). Bên trong hộp có thể có giá trị hoặc không.
Optional Binding
Cũng như forced unwrapping, Optional Binding cũng là một cách để "mở hộp"
var yearOfBirth: Int?
if let yearOfBirth = yearOfBirth {
var age = 2018 - yearOfBirth
print("Tuoi cua ban \\(age)")
}else {
print("yearOfBirth is nil")
}
Bạn chú ý đến keyword là if let. Tức là "nếu có".
Nếu biến yearOfBirth có giá trị thì gán giá trị đó cho hằng yearOfBirth ( bạn có thể dùng biến bằng cách đổi if let thành if var hoặc tên biến/hằng khác cũng được, mình dùng if let vì không thay đổi giá trị của yearOfBirth ). Hằng yearOfBirth này chỉ dùng được trong scope if. Ngược lại nếu biến yearOfBirth không có giá trị thì thưc hiện code trong scope else.
Implicitly Unwrapped Optionals
Với một biến kiểu Optional, muốn dùng nó ta phải forced unwrapping ( hoặc optional binding ). Ví dụ dùng ở 4 chỗ thì phải forced unwrapping 4 lần. Implicitly Unwrapped Optionals tức là forced unwrapping ngay lúc khởi tạo biến đó luôn.
Xem ví dụ sau để rõ hơn nhé:
Bình thường nếu tạo một UIImage từ một file name thì sẽ trả về kiểu UIImage?
Bởi vì khi lập trình bạn có thể gõ sai tên file hoặc thằng trong team nó đổi tên khác thì app sẽ crash.
Nhưng nếu bạn chắc chắn 100% tên file đúng thì thêm ! ngay lúc khai báo như sau
let appLogo: UIImage = UIImage(named: "girl")!
Như thế gọi là Implicitly Unwrapped Optionals. Sau này dùng hằng appLogo thì không cần forced unwrapping ( hoặc optional binding ) gì nữa hết.
Nil coalescing
Đây là cách để phòng hờ lỗi khi dùng biến kiểu Optional. Cách dùng như sau <Biến kiểu Optional> ?? <Giá trị mặc định>
var yearOfBirth: Int? print("yearOfBirth is \( yearOfBirth ?? 21 )")
Nếu yearOfBirth bị nil thì dùng giá trị 21, không thì dùng giá trị đã unwrapped của yearOfBirth
Optional Chaining
Định nghĩa từ Apple như sau:
Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be
nil
. If the optional contains a value, the property, method, or subscript call succeeds; if the optional isnil
, the property, method, or subscript call returnsnil
. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain isnil
.
Tóm lại là hơi khó hiểu, mình sẽ lấy một ví dụ sau:
class Person { var bankAccount: BankAccount? }
class BankAccount { var balance: Int? }
Một tài khoản ngân hàng thì có thể không có balance ( không đúng tự nhiên nhưng kệ :D )
Một người thì có thể có tài khoản ngân hàng hoặc khôngkhông
var person = Person()
if let bankAccount = person.bankAccount { if let balance = bankAccount.balance { print("balance = \(balance)") }else { print(print("không có balance")) } }else { print("không có bankAccount") }
Nếu từ từ dùng Optional binding như trên thì bạn sẽ biết được do không có tài khoản ngân hàng hay không có balance.
Nhưng nếu mục đích của bạn chỉ là cần biết balance là bao nhiêu thôi thì dùng như sau:
if let balance = person.bankAccount?.balance { print(balance) }else { print("Có thể không có bankAccount hoặc không có balance") }
Đây được gọi là Optional Chaining. Trong quá trình lấy balance, nếu bankAccount = nil hoặc balance = nil thì đều rơi vào scope else
Tức là nếu bạn có chain 100 cái optional cũng được, vẫn lấy được giá trị cuối của chuỗi ( optional chain ) đó, nhưng cái nào bị nil thì bạn không biết.
Kết
Hy vọng với bài viết khá đầy đủ này, bạn sẽ không còn lúng túng khi dùng ! hay ? trong Swift nữa.
Bạn cũng không cần nhớ tên kĩ thuật nó là Forced unwrapped, Optional Binding, Implicitly Unwrapped Optionals, Nil coalescing, Optional Chaining gì đâu. Nhớ thì tốt, không nhớ cũng chẳng sao, quan trọng là bạn hiểu.
Ở bài sau, chúng ta sẽ so sánh khi nào nên dùng if let và guard let nhé.
Bài viết cũng là tài liệu tham khảo thêm trong khóa học iOS với Swift.
Xem thêm các bài viết khác:
https://niviki.com/lo-trinh-hoc-swift-va-ios-co-ban-den-nang-cao/
https://niviki.com/quan-ly-bo-nho-trong-swift/