Câu trả lời này là wiki cộng đồng . Nếu bạn cảm thấy nó có thể được làm tốt hơn, hãy thoải mái chỉnh sửa nó !
Bối cảnh: Tùy chọn là gì?
Trong Swift, Optional
là một loại chung có thể chứa một giá trị (thuộc bất kỳ loại nào) hoặc không có giá trị nào cả.
Trong nhiều ngôn ngữ lập trình khác, một giá trị "sentinel" cụ thể thường được sử dụng để biểu thị sự thiếu giá trị . Trong Objective-C, ví dụ, nil
( con trỏ null ) biểu thị sự thiếu đối tượng. Nhưng điều này trở nên khó khăn hơn khi làm việc với các kiểu nguyên thủy - nên -1
được sử dụng để chỉ ra sự vắng mặt của một số nguyên, hoặc có lẽ INT_MIN
, hoặc một số nguyên khác? Nếu bất kỳ giá trị cụ thể nào được chọn có nghĩa là "không có số nguyên", điều đó có nghĩa là nó không còn có thể được coi là giá trị hợp lệ .
Swift là một ngôn ngữ an toàn loại, có nghĩa là ngôn ngữ 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 Chuỗi, loại an toàn sẽ ngăn bạn chuyển nhầm Int.
Trong Swift, bất kỳ loại nào cũng có thể được tùy chọn . Một giá trị tùy chọn có thể nhận bất kỳ giá trị nào từ loại ban đầu hoặc giá trị đặc biệt nil
.
Tùy chọn được xác định với một ?
hậu tố trên loại:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
Việc thiếu một giá trị trong một tùy chọn được chỉ định bởi nil
:
anOptionalInt = nil
(Lưu ý rằng điều này nil
không giống như nil
trong Objective-C. Trong Objective-C, nil
không có con trỏ đối tượng hợp lệ ; trong Swift, Tùy chọn không bị hạn chế đối với các loại đối tượng / tham chiếu. Tùy chọn hành xử tương tự như Có thể của Haskell .)
Tại sao tôi nhận được lỗi chết người: bất ngờ tìm thấy con số không trong khi hủy bỏ giá trị Tùy chọn ?
Để truy cập giá trị của một tùy chọn (nếu nó có một chút nào), bạn cần phải unwrap nó. Một giá trị tùy chọn có thể được mở ra một cách an toàn hoặc cưỡng bức. Nếu bạn buộc bỏ tùy chọn và nó không có giá trị, chương trình của bạn sẽ bị sập với thông báo trên.
Xcode sẽ cho bạn thấy sự cố bằng cách tô sáng một dòng mã. Vấn đề xảy ra trên dòng này.
Sự cố này có thể xảy ra với hai loại lực khác nhau:
1. Vô hiệu lực Unwrapping
Điều này được thực hiện với các !
nhà điều hành trên một tùy chọn. Ví dụ:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Lỗi nghiêm trọng: Bất ngờ tìm thấy con số không trong khi hủy bỏ giá trị Tùy chọn
Như anOptionalString
là nil
ở đây, bạn sẽ nhận được một vụ tai nạn trên đường, nơi bạn buộc Unwrap nó.
2. Các tùy chọn không bị che giấu
Chúng được định nghĩa bằng a !
, thay vì a ?
sau kiểu.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Các tùy chọn này được coi là có chứa một giá trị. Do đó, bất cứ khi nào bạn truy cập vào một tùy chọn ngầm định, nó sẽ tự động được buộc mở cho bạn. Nếu nó không chứa một giá trị, nó sẽ sụp đổ.
print(optionalDouble) // <- CRASH
Lỗi nghiêm trọng: Bất ngờ tìm thấy con số không trong khi hoàn toàn hủy bỏ một giá trị Tùy chọn
Để tìm ra biến nào gây ra sự cố, bạn có thể giữ ⌥trong khi nhấp để hiển thị định nghĩa, nơi bạn có thể tìm thấy loại tùy chọn.
IBOutlets, đặc biệt, thường là các tùy chọn không được bao bọc. Điều này là do xib hoặc bảng phân cảnh của bạn sẽ liên kết các cửa hàng khi chạy, sau khi khởi tạo. Do đó, bạn nên đảm bảo rằng bạn không truy cập vào các cửa hàng trước khi chúng được tải. Bạn cũng nên kiểm tra xem các kết nối có chính xác trong tệp bảng phân cảnh / xib của mình không, nếu không các giá trị sẽ nil
ở trạng thái chạy và do đó sẽ bị sập khi chúng bị ẩn . Khi sửa các kết nối, hãy thử xóa các dòng mã xác định các cửa hàng của bạn, sau đó kết nối lại chúng.
Khi nào tôi nên buộc Unrap tùy chọn?
Vô hiệu lực Unwrapping
Theo nguyên tắc chung, bạn không bao giờ nên ép buộc mở khóa tùy chọn với !
toán tử. Có thể có trường hợp sử dụng !
là chấp nhận được - nhưng bạn chỉ nên sử dụng nó nếu bạn chắc chắn 100% rằng tùy chọn có chứa một giá trị.
Mặc dù có thể có một dịp mà bạn có thể sử dụng vũ lực hủy ghép nối, như bạn biết rằng thực tế là một tùy chọn có chứa một giá trị - không có một nơi duy nhất mà bạn có thể gỡ bỏ tùy chọn đó một cách an toàn.
Các tùy chọn không bị che giấu
Các biến này được thiết kế để bạn có thể trì hoãn việc gán chúng cho đến sau trong mã của bạn. Nó là của bạn chịu trách nhiệm để đảm bảo họ có một giá trị trước khi bạn truy cập chúng. Tuy nhiên, vì chúng liên quan đến việc hủy bỏ lực lượng, chúng vẫn không an toàn - như họ giả định giá trị của bạn là không, mặc dù việc gán số không là hợp lệ.
Bạn chỉ nên sử dụng các tùy chọn chưa được tiết lộ như là phương sách cuối cùng . Nếu bạn có thể sử dụng một biến lười biếng , hoặc cung cấp một giá trị mặc định cho một biến - bạn nên làm như vậy thay vì sử dụng một tùy chọn hoàn toàn không được bao bọc.
Tuy nhiên, có một vài tình huống trong đó các tùy chọn không được bao bọc hoàn toàn có lợi và bạn vẫn có thể sử dụng nhiều cách khác nhau để hủy bỏ chúng một cách an toàn như được liệt kê dưới đây - nhưng bạn nên luôn thận trọng khi sử dụng chúng.
Làm thế nào tôi có thể đối phó một cách an toàn với Tùy chọn?
Cách đơn giản nhất để kiểm tra xem một tùy chọn có chứa giá trị hay không, là so sánh nó với nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Tuy nhiên, 99,9% thời gian khi làm việc với các tùy chọn, bạn thực sự sẽ muốn truy cập vào giá trị mà nó chứa, nếu nó chứa tất cả. Để làm điều này, bạn có thể sử dụng Binding tùy chọn .
Ràng buộc tùy chọn
Ràng buộc tùy chọn cho phép bạn kiểm tra xem một tùy chọn có chứa giá trị không - và cho phép bạn gán giá trị chưa được bao bọc cho một biến hoặc hằng mới. Nó sử dụng cú pháp if let x = anOptional {...}
hoặc if var x = anOptional {...}
, tùy thuộc vào việc bạn cần sửa đổi giá trị của biến mới sau khi ràng buộc nó.
Ví dụ:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Điều này trước tiên là kiểm tra xem tùy chọn có chứa giá trị không. Nếu đúng như vậy , thì giá trị 'không được bao bọc' được gán cho một biến mới ( number
) - sau đó bạn có thể tự do sử dụng như thể nó không phải là tùy chọn. Nếu tùy chọn không chứa giá trị, thì mệnh đề khác sẽ được gọi, như bạn mong đợi.
Điều gì gọn gàng về ràng buộc tùy chọn, là bạn có thể mở ra nhiều tùy chọn cùng một lúc. Bạn chỉ có thể tách các câu lệnh bằng dấu phẩy. Tuyên bố sẽ thành công nếu tất cả các tùy chọn đã được mở ra.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Một mẹo nhỏ khác là bạn cũng có thể sử dụng dấu phẩy để kiểm tra một điều kiện nhất định về giá trị, sau khi mở khóa.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Điều hấp dẫn duy nhất khi sử dụng ràng buộc tùy chọn trong câu lệnh if, là bạn chỉ có thể truy cập giá trị chưa được bao bọc từ trong phạm vi của câu lệnh. Nếu bạn cần quyền truy cập vào giá trị từ bên ngoài phạm vi của câu lệnh, bạn có thể sử dụng câu lệnh bảo vệ .
Một tuyên bố bảo vệ cho phép bạn xác định một điều kiện để thành công - và phạm vi hiện tại sẽ chỉ tiếp tục thực thi nếu điều kiện đó được đáp ứng. Chúng được định nghĩa với cú pháp guard condition else {...}
.
Vì vậy, để sử dụng chúng với một ràng buộc tùy chọn, bạn có thể làm điều này:
guard let number = anOptionalInt else {
return
}
(Lưu ý rằng trong thân bảo vệ, bạn phải sử dụng một trong các câu lệnh chuyển điều khiển để thoát khỏi phạm vi của mã hiện đang thực thi).
Nếu anOptionalInt
chứa một giá trị, nó sẽ được mở ra và gán cho number
hằng số mới . Mật mã sau khi bảo vệ sẽ tiếp tục thực thi. Nếu nó không chứa giá trị - người bảo vệ sẽ thực thi mã trong ngoặc, điều này sẽ dẫn đến việc chuyển quyền kiểm soát, do đó mã ngay sau đó sẽ không được thực thi.
Điều gọn gàng thực sự về các câu lệnh bảo vệ là giá trị chưa được bao bọc hiện có sẵn để sử dụng trong mã theo sau câu lệnh (như chúng ta biết rằng mã trong tương lai chỉ có thể thực thi nếu tùy chọn có giá trị). Đây là một cách tuyệt vời để loại bỏ 'kim tự tháp diệt vong' được tạo ra bằng cách lồng nhiều câu lệnh if.
Ví dụ:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Các lính canh cũng hỗ trợ các thủ thuật gọn gàng tương tự mà câu lệnh if hỗ trợ, chẳng hạn như hủy ghép nhiều tùy chọn cùng một lúc và sử dụng where
mệnh đề.
Việc bạn sử dụng câu lệnh if hay Guard hoàn toàn phụ thuộc vào việc bất kỳ mã nào trong tương lai có yêu cầu tùy chọn để chứa giá trị hay không.
Nhà điều hành hợp nhất Nil
Các Nil coalescing điều hành là một phiên bản tốc ký tiện lợi của nhà điều hành có điều kiện ternary , chủ yếu được thiết kế để chuyển đổi optionals để phi optionals. Nó có cú pháp a ?? b
, trong đó a
là một loại tùy chọn và b
cùng loại vớia
(mặc dù thường không phải là tùy chọn).
Về cơ bản, nó cho phép bạn nói Nếu a
có chứa một giá trị, hãy mở khóa. Nếu không thì hãy quay lại b
thay vì. Ví dụ: bạn có thể sử dụng nó như thế này:
let number = anOptionalInt ?? 0
Điều này sẽ xác định một number
hằng số Int
loại, sẽ chứa giá trị của anOptionalInt
, nếu nó chứa một giá trị, hoặc 0
nếu không.
Nó chỉ là viết tắt cho:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Chuỗi tùy chọn
Bạn có thể sử dụng Chuỗi tùy chọn để gọi một phương thức hoặc truy cập vào một thuộc tính trên một tùy chọn. Điều này được thực hiện đơn giản bằng cách thêm vào tên biến ?
khi sử dụng nó.
Ví dụ: giả sử chúng ta có một biến foo
, loại một Foo
thể hiện tùy chọn .
var foo : Foo?
Nếu chúng ta muốn gọi một phương thức foo
mà không trả về bất cứ điều gì, chúng ta chỉ cần làm:
foo?.doSomethingInteresting()
Nếu foo
chứa một giá trị, phương thức này sẽ được gọi trên nó. Nếu không, sẽ không có gì xấu xảy ra - mã sẽ tiếp tục thực thi.
(Đây là hành vi tương tự như gửi tin nhắn đến nil
trong Objective-C)
Do đó, điều này cũng có thể được sử dụng để thiết lập các thuộc tính cũng như các phương thức gọi. Ví dụ:
foo?.bar = Bar()
Một lần nữa, không có gì xấu sẽ xảy ra ở đây nếu foo
lànil
. Mã của bạn sẽ chỉ tiếp tục thực thi.
Một mẹo nhỏ khác mà chuỗi tùy chọn cho phép bạn thực hiện là kiểm tra xem việc đặt thuộc tính hoặc gọi phương thức có thành công hay không. Bạn có thể làm điều này bằng cách so sánh giá trị trả về nil
.
(Điều này là do một giá trị tùy chọn sẽ trả về Void?
thay vì Void
trên phương thức không trả về bất cứ thứ gì)
Ví dụ:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Tuy nhiên, mọi thứ trở nên khó khăn hơn một chút khi cố gắng truy cập các thuộc tính hoặc gọi các phương thức trả về giá trị. Bởi vì foo
là tùy chọn, mọi thứ được trả về từ nó cũng sẽ là tùy chọn. Để giải quyết vấn đề này, bạn có thể hủy bỏ các tùy chọn được trả về bằng một trong các phương thức trên - hoặc foo
tự mở khóa trước khi truy cập các phương thức hoặc gọi các phương thức trả về giá trị.
Ngoài ra, như tên cho thấy, bạn có thể 'xâu chuỗi' các câu lệnh này lại với nhau. Điều này có nghĩa là nếu foo
có một thuộc tính tùy chọn baz
, có thuộc tính qux
- bạn có thể viết như sau:
let optionalQux = foo?.baz?.qux
Một lần nữa, vì foo
và baz
là tùy chọn, giá trị được trả về qux
sẽ luôn là tùy chọn bất kể qux
chính nó là tùy chọn.
map
và flatMap
Một tính năng thường được sử dụng với các tùy chọn là khả năng sử dụng map
và các flatMap
chức năng. Điều này cho phép bạn áp dụng các biến đổi không tùy chọn cho các biến tùy chọn. Nếu một tùy chọn có giá trị, bạn có thể áp dụng một chuyển đổi nhất định cho nó. Nếu nó không có giá trị, nó sẽ vẫn còn nil
.
Ví dụ: giả sử bạn có một chuỗi tùy chọn:
let anOptionalString:String?
Bằng cách áp dụng map
hàm cho nó - chúng ta có thể sử dụng stringByAppendingString
hàm để nối nó với chuỗi khác.
Vì stringByAppendingString
có một đối số chuỗi không tùy chọn, chúng tôi không thể nhập trực tiếp chuỗi tùy chọn của mình. Tuy nhiên, bằng cách sử dụng map
, chúng ta có thể sử dụng cho phép stringByAppendingString
được sử dụng nếu anOptionalString
có giá trị.
Ví dụ:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Tuy nhiên, nếu anOptionalString
không có giá trị, map
sẽ trả về nil
. Ví dụ:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
hoạt động tương tự map
, ngoại trừ nó cho phép bạn trả lại tùy chọn khác từ bên trong thân đóng. Điều này có nghĩa là bạn có thể nhập một tùy chọn vào một quy trình yêu cầu đầu vào không tùy chọn, nhưng có thể tự xuất một tùy chọn.
try!
Hệ thống xử lý lỗi của Swift có thể được sử dụng một cách an toàn với Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Nếu someThrowingFunc()
ném lỗi, lỗi sẽ được bắt một cách an toàn trong catch
khối.
Các error
hằng số mà bạn nhìn thấy trong catch
khối đã không được công bố bởi chúng tôi - nó tự động được tạo ra bởi catch
.
Bạn cũng có thể error
tự khai báo , nó có lợi thế là có thể chuyển nó sang một định dạng hữu ích, ví dụ:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Sử dụng try
cách này là cách thích hợp để thử, bắt và xử lý các lỗi đến từ các chức năng ném.
Ngoài ra còn try?
có lỗi hấp thụ:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Nhưng hệ thống xử lý lỗi của Swift cũng cung cấp một cách để "buộc thử" với try!
:
let result = try! someThrowingFunc()
Các khái niệm được giải thích trong bài viết này cũng được áp dụng ở đây: nếu một lỗi được ném, ứng dụng sẽ bị sập.
Bạn chỉ nên sử dụng try!
nếu bạn có thể chứng minh rằng kết quả của nó sẽ không bao giờ thất bại trong bối cảnh của bạn - và điều này rất hiếm.
Hầu hết thời gian bạn sẽ sử dụng hệ thống Do-Try-Catch hoàn chỉnh - và tùy chọn try?
, trong những trường hợp hiếm hoi mà việc xử lý lỗi không quan trọng.
Tài nguyên