Tùy chọn trong Swift là loại có thể giữ giá trị hoặc không có giá trị. Tùy chọn được viết bằng cách nối thêm ?
bất kỳ loại nào:
var name: String? = "Bertie"
Tùy chọn (cùng với Generics) là một trong những khái niệm Swift khó hiểu nhất. Vì cách chúng được viết và sử dụng, thật dễ dàng để hiểu sai về những gì chúng là. So sánh các tùy chọn ở trên để tạo một chuỗi bình thường:
var name: String = "Bertie" // No "?" after String
Từ cú pháp, có vẻ như một Chuỗi tùy chọn rất giống với Chuỗi thông thường. Nó không thể. Chuỗi tùy chọn không phải là Chuỗi có bật cài đặt "tùy chọn". Nó không phải là một chuỗi đặc biệt. Chuỗi và Chuỗi tùy chọn là các loại hoàn toàn khác nhau.
Đây là điều quan trọng nhất cần biết: Một tùy chọn là một loại container. Chuỗi tùy chọn là một thùng chứa có thể chứa Chuỗi. Một Int tùy chọn là một thùng chứa có thể chứa một Int. Hãy nghĩ về một tùy chọn như một loại bưu kiện. Trước khi bạn mở nó (hoặc "mở khóa" bằng ngôn ngữ của các tùy chọn), bạn sẽ không biết liệu nó có chứa thứ gì hay không.
Bạn có thể xem cách các tùy chọn được triển khai trong Thư viện chuẩn Swift bằng cách nhập "Tùy chọn" vào bất kỳ tệp Swift nào và nhấp vào nó. Đây là phần quan trọng của định nghĩa:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Tùy chọn chỉ enum
là một trong hai trường hợp: .none
hoặc .some
. Nếu .some
đó là một giá trị liên quan, trong ví dụ trên, sẽ là String
"Xin chào". Một tùy chọn sử dụng Generics để đưa ra một loại cho giá trị liên quan. Loại một chuỗi tùy chọn không phải là String
, nó Optional
, hay chính xác hơnOptional<String>
.
Mọi thứ Swift làm với các tùy chọn là phép thuật giúp đọc và viết mã trôi chảy hơn. Thật không may, điều này che khuất cách nó thực sự hoạt động. Tôi sẽ trải qua một số thủ thuật sau.
Lưu ý: Tôi sẽ nói về các biến tùy chọn rất nhiều, nhưng cũng tốt để tạo các hằng tùy chọn. Tôi đánh dấu tất cả các biến bằng loại của chúng để giúp dễ hiểu hơn các loại loại được tạo, nhưng bạn không phải trong mã của riêng mình.
Cách tạo tùy chọn
Để tạo tùy chọn, hãy thêm ?
vào sau loại bạn muốn bọc. Bất kỳ loại có thể là tùy chọn, ngay cả các loại tùy chỉnh của riêng bạn. Bạn không thể có một khoảng cách giữa loại và ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Sử dụng tùy chọn
Bạn có thể so sánh một tùy chọn nil
để xem nó có giá trị không:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
Điều này hơi khó hiểu. Nó ngụ ý rằng một tùy chọn là một điều này hoặc điều khác. Đó là con số không hoặc là "Bob". Điều này không đúng, tùy chọn không chuyển thành thứ khác. So sánh nó với nil là một mẹo để làm cho mã dễ đọc hơn. Nếu một tùy chọn bằng 0, điều này chỉ có nghĩa là enum hiện được đặt thành .none
.
Chỉ có tùy chọn có thể được không
Nếu bạn cố gắng đặt một biến không tùy chọn thành không, bạn sẽ gặp lỗi.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Một cách khác để xem xét các tùy chọn là bổ sung cho các biến Swift thông thường. Chúng là đối tác của một biến được đảm bảo có giá trị. Swift là một ngôn ngữ cẩn thận ghét sự mơ hồ. Hầu hết các biến được định nghĩa là không tùy chọn, nhưng đôi khi điều này là không thể. Ví dụ, hãy tưởng tượng một bộ điều khiển xem tải hình ảnh từ bộ đệm hoặc từ mạng. Nó có thể có hoặc không có hình ảnh đó tại thời điểm bộ điều khiển xem được tạo. Không có cách nào để đảm bảo giá trị cho biến hình ảnh. Trong trường hợp này, bạn sẽ phải làm cho nó tùy chọn. Nó bắt đầu như nil
và khi hình ảnh được lấy ra, tùy chọn nhận được một giá trị.
Sử dụng một tùy chọn cho thấy các ý định lập trình viên. So với Objective-C, nơi mà bất kỳ đối tượng nào cũng có thể là con số không, Swift cần bạn rõ ràng về việc khi nào một giá trị có thể bị thiếu và khi nào nó được đảm bảo tồn tại.
Để sử dụng một tùy chọn, bạn "mở khóa" nó
Một tùy chọn String
không thể được sử dụng thay cho thực tế String
. Để sử dụng giá trị được bọc bên trong một tùy chọn, bạn phải mở khóa nó. Cách đơn giản nhất để mở khóa tùy chọn là thêm !
tên sau tùy chọn. Điều này được gọi là "buộc mở". Nó trả về giá trị bên trong tùy chọn (như loại ban đầu) nhưng nếu tùy chọn là nil
, nó gây ra sự cố thời gian chạy. Trước khi mở khóa, bạn nên chắc chắn có một giá trị.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Kiểm tra và sử dụng tùy chọn
Bởi vì bạn phải luôn kiểm tra số 0 trước khi hủy và sử dụng tùy chọn, đây là một mẫu phổ biến:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
Trong mẫu này, bạn kiểm tra xem có một giá trị nào không, sau đó khi bạn chắc chắn nó có giá trị, bạn buộc mở khóa nó thành một hằng số tạm thời để sử dụng. Vì đây là việc thường làm, Swift cung cấp lối tắt bằng cách sử dụng "nếu cho phép". Điều này được gọi là "ràng buộc tùy chọn".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Điều này tạo ra một hằng số tạm thời (hoặc biến nếu bạn thay thế let
bằng var
) có phạm vi là chỉ trong vòng nếu của niềng răng. Vì phải sử dụng một tên như "unsrappingMealPreference" hoặc "realMealPreference" là một gánh nặng, Swift cho phép bạn sử dụng lại tên biến ban đầu, tạo một tên tạm thời trong phạm vi khung
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Đây là một số mã để chứng minh rằng một biến khác nhau được sử dụng:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
Ràng buộc tùy chọn hoạt động bằng cách kiểm tra xem nếu tùy chọn bằng không. Nếu không, nó sẽ hủy tùy chọn thành hằng số được cung cấp và thực thi khối. Trong Xcode 8.3 trở lên (Swift 3.1), cố gắng in một tùy chọn như thế này sẽ gây ra một cảnh báo vô ích. Sử dụng tùy chọn debugDescription
để tắt tiếng:
print("\(mealPreference.debugDescription)")
Tùy chọn cho là gì?
Tùy chọn có hai trường hợp sử dụng:
- Những điều có thể thất bại (tôi đã mong đợi một cái gì đó nhưng tôi không có gì)
- Những thứ không có gì bây giờ nhưng có thể là thứ gì đó sau này (và ngược lại)
Một số ví dụ cụ thể:
- Một tài sản có thể ở đó hoặc không ở đó, như
middleName
hoặc spouse
trong một Person
lớp
- Một phương thức có thể trả về một giá trị hoặc không có gì, như tìm kiếm một kết quả khớp trong một mảng
- Một phương thức có thể trả về kết quả hoặc gặp lỗi và không trả về gì, như cố đọc nội dung của tệp (thường trả về dữ liệu của tệp) nhưng tệp không tồn tại
- Thuộc tính đại biểu, không phải luôn luôn được đặt và thường được đặt sau khi khởi tạo
- Đối với
weak
các thuộc tính trong các lớp. Điều họ chỉ đến có thể được đặt thành nil
bất cứ lúc nào
- Một nguồn tài nguyên lớn có thể phải được phát hành để lấy lại bộ nhớ
- Khi bạn cần một cách để biết khi nào một giá trị đã được đặt (dữ liệu chưa được tải> dữ liệu) thay vì sử dụng một dữ liệu riêng được tải
Boolean
Các tùy chọn không tồn tại trong Objective-C nhưng có một khái niệm tương đương, trả về con số không. Các phương thức có thể trả về một đối tượng có thể trả về nil thay vào đó. Điều này được hiểu là "sự vắng mặt của một đối tượng hợp lệ" và thường được sử dụng để nói rằng đã xảy ra sự cố. Nó chỉ hoạt động với các đối tượng Objective-C, không hoạt động với các kiểu C nguyên thủy hoặc cơ bản (enums, structs). Mục tiêu-C thường có các loại chuyên biệt để biểu thị sự vắng mặt của các giá trị này ( NSNotFound
thực sự NSIntegerMax
, kCLLocationCoordinate2DInvalid
để biểu thị tọa độ không hợp lệ -1
hoặc một số giá trị âm cũng được sử dụng). Lập trình viên phải biết về các giá trị đặc biệt này để chúng phải được ghi lại và học hỏi cho từng trường hợp. Nếu một phương thức không thể lấynil
làm tham số, thì điều này phải được ghi lại. Trong Mục tiêu-C,nil
là một con trỏ giống như tất cả các đối tượng được định nghĩa là con trỏ, nhưng là một nghĩa đen có nghĩa là sự vắng mặt của một loại nhất định.nil
trỏ đến một địa chỉ (không) cụ thể. Trong Swift,nil
So sánh với nil
Bạn đã từng có thể sử dụng bất kỳ tùy chọn nào dưới dạng Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
Trong các phiên bản gần đây hơn của Swift, bạn phải sử dụng leatherTrim != nil
. Tại sao lại thế này? Vấn đề là một Boolean
có thể được bọc trong một tùy chọn. Nếu bạn có Boolean
như thế này:
var ambiguous: Boolean? = false
nó có hai loại "sai", một loại không có giá trị và một loại có giá trị nhưng giá trị là false
. Swift ghét sự mơ hồ vì vậy bây giờ bạn phải luôn kiểm tra tùy chọn chống lại nil
.
Bạn có thể tự hỏi điểm của một tùy chọn Boolean
là gì? Cũng như các tùy chọn khác, .none
trạng thái có thể chỉ ra rằng giá trị vẫn chưa được biết. Có thể có một cái gì đó ở đầu bên kia của một cuộc gọi mạng cần một chút thời gian để thăm dò ý kiến. Booleans tùy chọn cũng được gọi là " Booleans ba giá trị "
Thủ thuật nhanh
Swift sử dụng một số thủ thuật để cho phép các tùy chọn hoạt động. Hãy xem xét ba dòng mã tùy chọn tìm kiếm thông thường này;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Không có dòng nào trong số này nên biên dịch.
- Dòng đầu tiên thiết lập một Chuỗi tùy chọn bằng cách sử dụng một chuỗi ký tự, hai loại khác nhau. Ngay cả khi đây là một
String
loại khác nhau
- Dòng thứ hai đặt một Chuỗi tùy chọn thành 0, hai loại khác nhau
- Dòng thứ ba so sánh một chuỗi tùy chọn với nil, hai loại khác nhau
Tôi sẽ xem qua một số chi tiết triển khai của các tùy chọn cho phép các dòng này hoạt động.
Tạo một tùy chọn
Sử dụng ?
để tạo một tùy chọn là đường cú pháp, được kích hoạt bởi trình biên dịch Swift. Nếu bạn muốn thực hiện theo cách lâu dài, bạn có thể tạo một tùy chọn như thế này:
var name: Optional<String> = Optional("Bob")
Trình Optional
khởi tạo đầu tiên này gọi , khởi tạo public init(_ some: Wrapped)
kiểu liên kết của tùy chọn từ loại được sử dụng trong ngoặc đơn.
Cách thậm chí dài hơn để tạo và thiết lập một tùy chọn:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Đặt tùy chọn thành nil
Bạn có thể tạo một tùy chọn không có giá trị ban đầu hoặc tạo một tùy chọn có giá trị ban đầu là nil
(cả hai đều có cùng kết quả).
var name: String?
var name: String? = nil
nil
Giao thức cho phép bằng nhau được kích hoạt bởi giao thức ExpressibleByNilLiteral
(được đặt tên trước đó NilLiteralConvertible
). Tùy chọn được tạo bằng trình Optional
khởi tạo thứ hai , public init(nilLiteral: ())
. Các tài liệu nói rằng bạn không nên sử dụng ExpressibleByNilLiteral
cho bất cứ điều gì ngoại trừ tùy chọn, vì điều đó sẽ thay đổi ý nghĩa của con số không trong mã của bạn, nhưng bạn có thể làm điều đó:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
Giao thức tương tự cho phép bạn đặt tùy chọn đã được tạo thành nil
. Mặc dù điều đó không được khuyến khích, bạn có thể sử dụng trình khởi tạo bằng chữ không trực tiếp:
var name: Optional<String> = Optional(nilLiteral: ())
So sánh một tùy chọn với nil
Tùy chọn xác định hai toán tử "==" và "! =" Đặc biệt mà bạn có thể thấy trong Optional
định nghĩa. Cái đầu tiên ==
cho phép bạn kiểm tra xem có tùy chọn nào bằng 0 không. Hai tùy chọn khác nhau được đặt thành .none sẽ luôn bằng nhau nếu các loại liên kết giống nhau. Khi bạn so sánh với con số không, đằng sau hậu trường, Swift tạo ra một tùy chọn cùng loại được liên kết, được đặt thành .none sau đó sử dụng tùy chọn đó để so sánh.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
==
Toán tử thứ hai cho phép bạn so sánh hai tùy chọn. Cả hai phải cùng loại và loại đó cần tuân thủ Equatable
(giao thức cho phép so sánh mọi thứ với toán tử "==" thông thường). Swift (có lẽ) mở ra hai giá trị và so sánh chúng trực tiếp. Nó cũng xử lý trường hợp một hoặc cả hai tùy chọn.none
. Lưu ý phân biệt giữa so sánh với nil
nghĩa đen.
Hơn nữa, nó cho phép bạn so sánh bất kỳ Equatable
loại nào với một gói tùy chọn loại đó:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
Đằng sau hậu trường, Swift kết thúc việc không tùy chọn là một tùy chọn trước khi so sánh. Nó cũng hoạt động với nghĩa đen ( if 23 == numberFromString {
)
Tôi đã nói có hai ==
toán tử, nhưng thực sự có một phần ba cho phép bạn đặt nil
ở phía bên trái của so sánh
if nil == name { ... }
Tùy chọn đặt tên
Không có quy ước Swift để đặt tên các loại tùy chọn khác với các loại không tùy chọn. Mọi người tránh thêm một cái gì đó vào tên để cho thấy rằng đó là một tùy chọn (như "tùy chọnMiddleName" hoặc "ossibleNumberAsString") và để khai báo cho thấy đó là một loại tùy chọn. Điều này trở nên khó khăn khi bạn muốn đặt tên một cái gì đó để giữ giá trị từ một tùy chọn. Tên "middleName" ngụ ý rằng đó là loại Chuỗi, vì vậy khi bạn trích xuất giá trị Chuỗi từ nó, bạn thường có thể kết thúc bằng các tên như "factMiddleName" hoặc "unsrappingMiddleName" hoặc "realMiddleName". Sử dụng ràng buộc tùy chọn và sử dụng lại tên biến để khắc phục điều này.
Định nghĩa chính thức
Từ "Những điều cơ bản" trong ngôn ngữ lập trình Swift :
Swift cũng giới thiệu các loại tùy chọn, xử lý việc không có giá trị. Các tùy chọn cho biết, hoặc có một giá trị, và nó tương đương với x hoặc hoặc không có giá trị nào cả. Các tùy chọn tương tự như sử dụng nil với các con trỏ trong Objective-C, nhưng chúng hoạt động cho bất kỳ loại nào, không chỉ các lớp. Các tùy chọn an toàn và biểu cảm hơn các con trỏ không trong Objective-C và là trung tâm của nhiều tính năng mạnh mẽ nhất của Swift.
Tùy chọn là một ví dụ về thực tế rằng Swift là một ngôn ngữ an toàn. Swift giúp bạn rõ ràng về các loại giá trị mà mã của bạn có thể làm việc với. Nếu một phần mã của bạn mong đợi một Chuỗi, loại an toàn sẽ ngăn bạn chuyển nhầm Int. Điều này cho phép bạn bắt và sửa lỗi càng sớm càng tốt trong quá trình phát triển.
Để kết thúc, đây là một bài thơ từ năm 1899 về các tùy chọn:
Hôm qua trên cầu thang
tôi đã gặp một người đàn ông không ở đó
Anh ấy không ở đó hôm nay
tôi ước, tôi ước anh ấy sẽ đi
Antigonish
Nhiêu tai nguyên hơn: