“Lỗi nghiêm trọng: Không tìm thấy bất ngờ khi mở gói một giá trị Tùy chọn” nghĩa là gì?
Chương trình Swift của tôi đang gặp sự cố EXC_BAD_INSTRUCTION
và một trong các lỗi tương tự sau đây. Lỗi này có nghĩa là gì và làm cách nào để khắc phục?
Lỗi nghiêm trọng: Không tìm thấy bất ngờ khi mở gói một giá trị Tùy chọn
hoặc là
Lỗi nghiêm trọng: Không được tìm thấy bằng nil trong khi ngầm định bỏ gói một giá trị Tùy chọn
Bài đăng này nhằm mục đích thu thập câu trả lời cho các vấn đề "số không được tìm thấy bất ngờ", để chúng không bị phân tán và khó tìm. Vui lòng thêm câu trả lời của riêng bạn hoặc chỉnh sửa câu trả lời wiki hiện có.
Trả lời
Câu trả lời này là wiki cộng đồng . Nếu bạn cảm thấy nó có thể được cải thiện 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<Wrapped>
là một kiểu tùy chọn : nó có thể chứa bất kỳ giá trị nào từ kiểu ban đầu ("Được bao bọc") hoặc không có giá trị nào cả (giá trị đặc biệt nil
). Một giá trị bắt buộc phải có quà nào trước khi nó có thể được sử dụng.
Tùy chọn là loại chung chung , có nghĩa là Optional<Int>
và Optional<String>
là các loại riêng biệt - loại bên trong <>
được gọi là loại Được bọc. Dưới mui xe, Tùy chọn là một enum có hai trường hợp: .some(Wrapped)
và .none
, trong đó .none
tương đương với nil
.
Các tùy chọn có thể được khai báo bằng cách sử dụng kiểu được đặt tên Optional<T>
, hoặc (phổ biến nhất) dưới dạng tốc ký có ?
hậu tố.
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int> // equivalent to `Int?`
anOptionalInt = nil // now this variable contains nil instead of an integer
Tùy chọn là một công cụ đơn giản nhưng mạnh mẽ để thể hiện các giả định của bạn trong khi viết mã. Trình biên dịch có thể sử dụng thông tin này để ngăn bạn mắc lỗi. Từ ngôn ngữ lập trình Swift :
Swift là một ngôn ngữ an toàn về kiểu chữ , có nghĩa là ngôn ngữ giúp bạn hiểu rõ về các loại giá trị mà mã của bạn có thể hoạt động. Nếu một phần của mã của bạn yêu cầu một
String
, loại an toàn sẽ ngăn bạn chuyểnInt
nhầm. Tương tự như vậy, an toàn kiểu ngăn bạn vô tình chuyển một tùy chọnString
sang một đoạn mã yêu cầu không tùy chọnString
. An toàn kiểu giúp bạn bắt và sửa lỗi sớm nhất có thể trong quá trình phát triển.
Một số ngôn ngữ lập trình khác cũng có các loại tùy chọn chung : ví dụ: Có thể trong Haskell, tùy chọn trong Rust và tùy chọn trong C ++ 17.
Trong các ngôn ngữ lập trình không có loại tùy chọn, một giá trị "sentinel" cụ thể thường được sử dụng để chỉ ra sự không có giá trị hợp lệ. Ví dụ, trong Objective-C, nil
( con trỏ null ) đại diện cho việc thiếu một đối tượng. Đối với các kiểu nguyên thủy chẳng hạn int
, con trỏ null không thể được sử dụng, vì vậy bạn sẽ cần một biến riêng biệt (chẳng hạn như value: Int
và isValid: Bool
) hoặc một giá trị sentinel được chỉ định (chẳng hạn như -1
hoặc INT_MIN
). Những cách tiếp cận này dễ xảy ra lỗi bởi vì bạn dễ quên kiểm tra isValid
hoặc kiểm tra giá trị của các trọng điểm. Ngoài ra, nếu một giá trị cụ thể được chọn làm trọng điểm, điều đó có nghĩa là nó không còn được coi là giá trị hợp lệ nữa.
Các kiểu tùy chọn như Swift Optional
giải quyết những vấn đề này bằng cách giới thiệu một nil
giá trị đặc biệt, riêng biệt (vì vậy bạn không phải chỉ định giá trị sentinel) và bằng cách tận dụng hệ thống kiểu mạnh để trình biên dịch có thể giúp bạn nhớ kiểm tra nil khi cần thiết.
Tại sao tôi gặp " lỗi nghiêm trọng: không tìm thấy bất ngờ khi mở gói một 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 mở một tùy chọn và nó không có giá trị, chương trình của bạn sẽ gặp sự cố với thông báo trên.
Xcode sẽ cho bạn thấy sự cố bằng cách đánh dấu một dòng mã. Sự cố 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 mở khác nhau:
1. Giải nén lực lượng rõ ràng
Điều này được thực hiện với !
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: Không tìm thấy bất ngờ khi mở gói một 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. Tùy chọn không được gói gọn
Chúng được định nghĩa bằng một !
, thay vì một ?
sau loại.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Các tùy chọn này được giả định 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 không được bao bọc hoàn toàn, nó sẽ tự động bị buộc mở cho bạn. Nếu không chứa giá trị, nó sẽ bị lỗi.
print(optionalDouble) // <- CRASH
Lỗi nghiêm trọng: Không được tìm thấy bằng nil trong khi ngầm định bỏ gói 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ữ ⌥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.
Đặc biệt, IBOutlet thường là các tùy chọn không được bao bọc ngầm. Đ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 trong thời gian 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 các cửa hàng trước khi chúng được tải vào. Bạn cũng nên kiểm tra xem các kết nối có chính xác trong tệp storyboard / xib của mình hay không, nếu không các giá trị sẽ nil
ở trong thời gian chạy và do đó sẽ bị lỗi khi chúng được mở ra một cách ngầm định . 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ắm của bạn, sau đó kết nối lại chúng.
Khi nào tôi nên buộc mở một tùy chọn?
Mở gói lực lượng rõ ràng
Theo nguyên tắc chung, bạn không bao giờ được buộc mở một cách rõ ràng một tùy chọn với !
toán tử. Có thể có những trường hợp việc sử dụng !
được chấp nhận - 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ị.
Trong khi có thể là một dịp mà bạn có thể sử dụng vũ lực unwrapping, như bạn đã biết cho một thực tế rằng một tùy chọn chứa một giá trị - đó không phải là một đơn nơi mà bạn có thể không an toàn Unwrap rằng tùy chọn để thay thế.
Tùy chọn không được gói gọn
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 này 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 buộc mở gói, chúng vẫn không an toàn - vì chúng cho rằng giá trị của bạn không phải là nil, mặc dù việc gán nil là hợp lệ.
Bạn chỉ nên sử dụng các tùy chọn không được bao bọc hoàn toàn như một phương sách cuối cùng . Nếu bạn có thể sử dụng biến lười hoặc cung cấp giá trị mặc định cho một biến - bạn nên làm như vậy thay vì sử dụng tùy chọn không được bao bọc hoàn toàn.
Tuy nhiên, có một số trường hợp trong đó các tùy chọn không được đóng gói ngầm là có lợi và bạn vẫn có thể sử dụng nhiều cách khác nhau để mở gói chúng một cách an toàn như được liệt kê bên dưới - nhưng bạn nên luôn sử dụng chúng một cách thận trọng.
Làm cách nào để tôi có thể giải quyết các tùy chọn một cách an toàn?
Cách đơn giản nhất để kiểm tra xem một tùy chọn có chứa một 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 sẽ thực sự muốn truy cập vào giá trị mà nó chứa, nếu nó hoàn toàn chứa một. Để làm điều này, bạn có thể sử dụng Ràng buộc 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 tùy chọn có chứa giá trị hay 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 số mới. Nó sử dụng cú pháp if let x = anOptional {...}
hoặc if var x = anOptional {...}
, tùy thuộc nếu 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 làm 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ị 'chưa được gói' được gán cho một biến mới ( number
) - sau đó bạn có thể thoải mái 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 đề else sẽ được gọi, như bạn mong đợi.
Điều gọn gàng về ràng buộc tùy chọn là bạn có thể mở 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. Câu lệnh 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 trên giá trị, sau khi mở gói.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Điểm 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 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 cho sự thành công - và phạm vi hiện tại sẽ chỉ tiếp tục thực hiện nếu điều kiện được đáp ứng. Chúng được định nghĩa bằng 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 như sau:
guard let number = anOptionalInt else {
return
}
(Lưu ý rằng trong cơ quan 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 . Đoạn mã sau khi bảo vệ sau đó sẽ tiếp tục thực thi. Nếu nó không chứa giá trị - trình bảo vệ sẽ thực thi mã trong dấu ngoặc, điều này sẽ dẫn đến việc chuyển quyền điều khiển, 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à 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 bảo vệ cũng hỗ trợ các thủ thuật tương tự như câu lệnh if đã hỗ trợ, chẳng hạn như bỏ gói 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 bảo vệ hoàn toàn phụ thuộc vào việc có bất kỳ mã nào trong tương lai yêu cầu tùy chọn chứa giá trị hay không.
Nil Coalescing Operator
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 một loại với a
(mặc dù thường là không tùy chọn).
Về cơ bản, nó cho phép bạn nói “Nếu a
chứa một giá trị, hãy mở nó ra. Nếu không thì hãy quay lại b
thay thế ”. 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
kiểu, 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 của:
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 một thuộc tính trên một tùy chọn. Điều này đơn giản được thực hiện bằng cách thêm tên biến với một ?
khi sử dụng nó.
Ví dụ: giả sử chúng ta có một biến foo
, kiểu một Foo
trường hợp tùy chọn .
var foo : Foo?
Nếu chúng ta muốn gọi một phương thức foo
không trả về bất cứ điều gì, chúng ta có thể chỉ cần thực hiện:
foo?.doSomethingInteresting()
Nếu foo
chứa một giá trị, phương thức này sẽ được gọi trê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
có nil
. Mã của bạn sẽ 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 một 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ề với nil
.
(Điều này là do một giá trị tùy chọn sẽ trả về Void?
thay vì Void
trên một phương thức không trả về bất kỳ 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 phức tạp 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ề một giá trị. Vì foo
là tùy chọn, bất kỳ thứ gì trả về từ nó cũng sẽ là tùy chọn. Để giải quyết vấn đề này, bạn có thể mở gói các tùy chọn được trả về bằng một trong các phương thức trên - hoặc mở foo
chính nó trước khi truy cập các phương thức hoặc phương thức gọi trả về giá trị.
Ngoài ra, như tên cho thấy, bạn có thể '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ó 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, bởi vì foo
và baz
là tùy chọn, giá trị trả về từ qux
sẽ luôn là tùy chọn bất kể qux
chính nó có phải là tùy chọn hay không.
map
và flatMap
Một tính năng thường ít được sử dụng với các tùy chọn là khả năng sử dụng các chức năng map
và flatMap
. Nhữ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ó một giá trị, bạn có thể áp dụng một biế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 một chuỗi khác.
Vì stringByAppendingString
nhận đối số chuỗi không phải 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 allow stringByAppendingString
to be used 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ự như map
, ngoại trừ nó cho phép bạn trả về một tùy chọn khác từ bên trong phần 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 an toàn với Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Nếu someThrowingFunc()
ném một 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ể truyề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 hàm 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 đăng này cũng áp dụng ở đây: nếu một lỗi được tạo ra, ứ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à hệ thống tùy chọn try?
, trong một số ít trường hợp mà việc xử lý lỗi không quan trọng.
Tài nguyên
TL; câu trả lời DR
Với rất ít trường hợp ngoại lệ , quy tắc này là vàng:
Tránh sử dụng !
Khai báo biến tùy chọn ( ?
), không hoàn toàn là tùy chọn không được gói (IUO) ( !
)
Nói cách khác, thay vì sử dụng:
var nameOfDaughter: String?
Thay vì:
var nameOfDaughter: String!
Bỏ gói biến tùy chọn bằng cách sử dụng if let
hoặcguard let
Biến mở rộng như thế này:
if let nameOfDaughter = nameOfDaughter {
print("My daughters name is: \(nameOfDaughter)")
}
Hoặc như thế này:
guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")
Câu trả lời này được thiết kế để ngắn gọn, để đọc hiểu đầy đủ câu trả lời được chấp nhận
Tài nguyên
Câu hỏi này xuất hiện MỌI LÚC vào SO. Đó là một trong những điều đầu tiên mà các nhà phát triển Swift mới phải vật lộn.
Lý lịch:
Swift sử dụng khái niệm "Tùy chọn" để xử lý các giá trị có thể chứa một giá trị hoặc không. Trong các ngôn ngữ khác như C, bạn có thể lưu trữ giá trị 0 trong một biến để biểu thị rằng nó không chứa giá trị nào. Tuy nhiên, nếu 0 là giá trị hợp lệ thì sao? Sau đó, bạn có thể sử dụng -1. Điều gì xảy ra nếu -1 là một giá trị hợp lệ? Và như thế.
Swift tùy chọn cho phép bạn thiết lập một biến thuộc bất kỳ loại nào để chứa giá trị hợp lệ hoặc không có giá trị.
Bạn đặt dấu chấm hỏi sau kiểu khi bạn khai báo một biến là có nghĩa (nhập x hoặc không có giá trị).
Một tùy chọn thực sự là một vùng chứa hơn là chứa một biến của một kiểu nhất định hoặc không có gì.
Một tùy chọn cần được "mở ra" để lấy giá trị bên trong.
Các "!" toán tử là một toán tử "buộc tháo". Nó nói "tin tôi đi. Tôi biết mình đang làm gì. Tôi đảm bảo rằng khi mã này chạy, biến sẽ không chứa nil." Nếu bạn sai, bạn sẽ sụp đổ.
Trừ khi bạn thực sự làm biết những gì bạn đang làm, tránh những "!" buộc người điều khiển mở. Nó có lẽ là nguồn gây ra lỗi lớn nhất cho những người mới bắt đầu lập trình Swift.
Cách đối phó với các tùy chọn:
Có rất nhiều cách khác để giải quyết các tùy chọn an toàn hơn. Đây là một số (không phải là danh sách đầy đủ)
Bạn có thể sử dụng "liên kết tùy chọn" hoặc "if let" để nói "nếu tùy chọn này chứa một giá trị, hãy lưu giá trị đó vào một biến mới, không phải tùy chọn. Nếu tùy chọn không chứa giá trị, hãy bỏ qua phần nội dung của câu lệnh if này ".
Đây là một ví dụ về ràng buộc tùy chọn với foo
tùy chọn của chúng tôi :
if let newFoo = foo //If let is called optional binding. {
print("foo is not nil")
} else {
print("foo is nil")
}
Lưu ý rằng biến mà bạn xác định khi sử dụng biding tùy chọn chỉ tồn tại (chỉ "trong phạm vi") trong phần nội dung của câu lệnh if.
Ngoài ra, bạn có thể sử dụng câu lệnh bảo vệ, cho phép bạn thoát khỏi hàm của mình nếu biến là nil:
func aFunc(foo: Int?) {
guard let newFoo = input else { return }
//For the rest of the function newFoo is a non-optional var
}
Các câu lệnh Guard đã được thêm vào trong Swift 2. Guard cho phép bạn duy trì "đường dẫn vàng" thông qua mã của mình và tránh mức độ ngày càng tăng của các if lồng nhau đôi khi là kết quả của việc sử dụng ràng buộc tùy chọn "if let".
Ngoài ra còn có một cấu trúc được gọi là "toán tử liên kết nil". Nó có dạng "option_var ?? Replace_val". Nó trả về một biến không phải tùy chọn có cùng kiểu với dữ liệu có trong tùy chọn. Nếu tùy chọn chứa nil, nó trả về giá trị của biểu thức sau dấu "??" Biểu tượng.
Vì vậy, bạn có thể sử dụng mã như sau:
let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")
Bạn cũng có thể sử dụng xử lý lỗi thử / bắt hoặc bảo vệ, nhưng nhìn chung một trong các kỹ thuật khác ở trên là sạch hơn.
BIÊN TẬP:
Một kiểu gotcha khác, tinh tế hơn một chút với các tùy chọn là "các tùy chọn không được gói ngầm. Khi chúng tôi khai báo foo, chúng tôi có thể nói:
var foo: String!
Trong trường hợp đó, foo vẫn là tùy chọn, nhưng bạn không cần phải mở nó ra để tham chiếu. Điều đó có nghĩa là bất cứ khi nào bạn cố gắng tham chiếu foo, bạn sẽ gặp sự cố nếu nó là 0.
Vì vậy, mã này:
var foo: String!
let upperFoo = foo.capitalizedString
Sẽ gặp sự cố khi tham chiếu đến thuộc tính capitalizedString của foo mặc dù chúng tôi không bắt buộc mở foo. bản in trông đẹp, nhưng nó không phải.
Vì vậy, bạn muốn thực sự cẩn thận với các tùy chọn không được đóng gói ngầm. (và thậm chí có thể tránh chúng hoàn toàn cho đến khi bạn có hiểu biết vững chắc về các tùy chọn.)
Điểm mấu chốt: Khi bạn lần đầu tiên học Swift, hãy giả sử dấu "!" ký tự không phải là một phần của ngôn ngữ. Nó có thể khiến bạn gặp rắc rối.
Vì các câu trả lời trên giải thích rõ ràng làm thế nào để chơi một cách an toàn với Tùy chọn. Tôi sẽ cố gắng giải thích những gì Tùy chọn thực sự nhanh chóng.
Một cách khác để khai báo một biến tùy chọn là
var i : Optional<Int>
Và Loại tùy chọn không là gì ngoài một kiểu liệt kê có hai trường hợp, tức là
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
.
.
.
}
Vì vậy, để gán nil cho biến 'i' của chúng ta. Chúng tôi có thể thực hiện
var i = Optional<Int>.none
hoặc chỉ định một giá trị, chúng tôi sẽ chuyển một số giá trị
var i = Optional<Int>.some(28)
Theo swift, 'nil' là sự không có giá trị. Và để tạo một thể hiện được khởi tạo với nil
Chúng tôi phải tuân theo một giao thức được gọi ExpressibleByNilLiteral
và tuyệt vời nếu bạn đoán được nó, chỉ khuyến khích Optionals
tuân theo ExpressibleByNilLiteral
và phù hợp với các kiểu khác.
ExpressibleByNilLiteral
có một phương thức duy nhất được gọi là init(nilLiteral:)
khởi tạo một thể hiện bằng nil. Bạn thường sẽ không gọi phương thức này và theo tài liệu nhanh, không khuyến khích gọi trực tiếp trình khởi tạo này vì trình biên dịch gọi nó bất cứ khi nào bạn khởi tạo một kiểu Tùy chọn bằng nil
chữ.
Ngay cả bản thân tôi cũng phải quấn (không có ý định chơi chữ) đầu của tôi xung quanh Tùy chọn: D Happy Swfting All .
Trước tiên, bạn nên biết giá trị Tùy chọn là gì. Bạn có thể chuyển sang Ngôn ngữ lập trình Swift để biết thêm chi tiết.
Thứ hai, bạn nên biết giá trị tùy chọn có hai trạng thái. Một là giá trị đầy đủ và một là giá trị nil. Vì vậy, trước khi triển khai một giá trị tùy chọn, bạn nên kiểm tra trạng thái của nó.
Bạn có thể sử dụng if let ...
hoặc guard let ... else
v.v.
Một cách khác, nếu bạn không muốn kiểm tra trạng thái biến trước khi triển khai, bạn cũng có thể sử dụng var buildingName = buildingName ?? "buildingName"
thay thế.
Tôi đã gặp lỗi này một lần khi tôi đang cố gắng đặt các giá trị Outlets của mình từ phương thức chuẩn bị cho segue như sau:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// This line pops up the error
destination.nameLabel.text = item.name
}
}
}
Sau đó, tôi phát hiện ra rằng tôi không thể đặt giá trị của các cửa hàng bộ điều khiển đích vì bộ điều khiển chưa được tải hoặc khởi tạo.
Vì vậy, tôi đã giải quyết nó theo cách này:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// Created this method in the destination Controller to update its outlets after it's being initialized and loaded
destination.updateView(itemData: item)
}
}
}
Bộ điều khiển đích:
// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""
// Outlets
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
nameLabel.text = name
}
func updateView(itemDate: ObjectModel) {
name = itemDate.name
}
Tôi hy vọng câu trả lời này sẽ giúp ích cho bất kỳ ai có cùng vấn đề như tôi nhận thấy câu trả lời được đánh dấu là nguồn tài nguyên tuyệt vời để hiểu các tùy chọn và cách chúng hoạt động nhưng chưa giải quyết trực tiếp vấn đề.
Về cơ bản, bạn đã cố gắng sử dụng giá trị nil ở những nơi Swift chỉ cho phép những giá trị không phải nil, bằng cách nói với trình biên dịch tin tưởng bạn rằng sẽ không bao giờ có giá trị nil ở đó, do đó cho phép ứng dụng của bạn biên dịch.
Có một số trường hợp dẫn đến loại lỗi nghiêm trọng này:
buộc phải mở:
let user = someVariable!
Nếu
someVariable
là con số không, thì bạn sẽ gặp sự cố. Bằng cách thực hiện một lực mở, bạn đã chuyển trách nhiệm kiểm tra nil từ trình biên dịch sang cho bạn, về cơ bản bằng cách thực hiện một thao tác mở buộc bạn đang đảm bảo với trình biên dịch rằng bạn sẽ không bao giờ có giá trị nil ở đó. Và đoán xem điều gì sẽ xảy ra nếu bằng cách nào đó giá trị nil kết thúc bằngsomeVariable
?Giải pháp? Sử dụng liên kết tùy chọn (còn gọi là if-let), thực hiện xử lý biến ở đó:
if user = someVariable { // do your stuff }
ép buộc (xuống) phôi:
let myRectangle = someShape as! Rectangle
Tại đây bằng cách ép buộc bạn yêu cầu trình biên dịch không còn lo lắng nữa, vì bạn sẽ luôn có một
Rectangle
phiên bản ở đó. Và miễn là điều đó được giữ vững, bạn không phải lo lắng. Các vấn đề bắt đầu khi bạn hoặc đồng nghiệp của bạn từ dự án bắt đầu luân chuyển các giá trị không phải hình chữ nhật.Giải pháp? Sử dụng liên kết tùy chọn (còn gọi là if-let), thực hiện xử lý biến ở đó:
if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle }
Các tùy chọn không được bao bọc hoàn toàn. Giả sử bạn có định nghĩa lớp sau:
class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } }
Bây giờ, nếu không ai gây rối với thuộc
name
tính bằng cách đặt nó thànhnil
, thì nó hoạt động như mong đợi, tuy nhiên nếuUser
được khởi tạo từ JSON thiếuname
khóa, thì bạn sẽ gặp lỗi nghiêm trọng khi cố gắng sử dụng thuộc tính.Giải pháp? Đừng sử dụng chúng :) Trừ khi bạn chắc chắn 102% rằng thuộc tính sẽ luôn có giá trị khác 0 vào thời điểm nó cần được sử dụng. Trong hầu hết các trường hợp, chuyển đổi sang tùy chọn hoặc không tùy chọn sẽ hoạt động. Làm cho nó không phải là tùy chọn cũng sẽ dẫn đến việc trình biên dịch giúp bạn bằng cách cho biết các đường dẫn mã mà bạn đã bỏ lỡ khi cung cấp giá trị cho thuộc tính đó
Các cửa hàng chưa được kết nối, hoặc chưa được kết nối. Đây là một trường hợp cụ thể của tình huống số 3. Về cơ bản, bạn có một số lớp được tải XIB mà bạn muốn sử dụng.
class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! }
Bây giờ, nếu bạn lỡ kết nối ổ cắm từ trình chỉnh sửa XIB, thì ứng dụng sẽ gặp sự cố ngay khi bạn muốn sử dụng ổ cắm. Giải pháp? Đảm bảo tất cả các ổ cắm đều được kết nối. Hoặc sử dụng các
?
toán tử trên chúng:emailTextField?.text = "[email protected]"
. Hoặc khai báo lối ra là tùy chọn, mặc dù trong trường hợp này, trình biên dịch sẽ buộc bạn phải mở nó ra trên toàn bộ mã.Giá trị đến từ Objective-C và không có chú thích giá trị rỗng. Giả sử chúng ta có lớp Objective-C sau:
@interface MyUser: NSObject @property NSString *name; @end
Bây giờ nếu không có chú thích nào được chỉ định (hoặc rõ ràng hoặc thông qua
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
), thì thuộcname
tính sẽ được nhập trong Swift dưới dạngString!
(một IUO - tùy chọn không được bao bọc ngầm). Ngay sau khi một số mã nhanh muốn sử dụng giá trị, nó sẽ bị lỗi nếuname
là số không.Giải pháp? Thêm chú thích vô hiệu vào mã Objective-C của bạn. Tuy nhiên, hãy lưu ý, trình biên dịch Objective-C hơi dễ dãi khi nói đến khả năng vô hiệu, bạn có thể kết thúc với các giá trị nil, ngay cả khi bạn đã đánh dấu rõ ràng chúng là
nonnull
.
Đây là một nhận xét quan trọng hơn và lý do tại sao các tùy chọn không được bao bọc ngầm lại có thể là lừa đảo khi nói đến nil
các giá trị gỡ lỗi .
Hãy nghĩ về đoạn mã sau: Nó biên dịch không có lỗi / cảnh báo:
c1.address.city = c3.address.city
Tuy nhiên, trong thời gian chạy, nó đưa ra lỗi sau: Lỗi nghiêm trọng: Không tìm thấy bất ngờ trong khi mở gói một giá trị Tùy chọn
Bạn có thể cho tôi biết là đối tượng nil
nào?
Bạn không thể!
Mã đầy đủ sẽ là:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c3 = BadContact()
c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct BadContact {
var address : Address!
}
struct Address {
var city : String
}
Truyện dài ngắn bằng cách sử dụng, var address : Address!
bạn đang che giấu khả năng có thể có một biến số nil
từ những người đọc khác. Và khi nó gặp sự cố, bạn sẽ giống như "cái quái gì vậy ?! của tôi address
không phải là tùy chọn, vậy tại sao tôi lại gặp sự cố?!.
Do đó, tốt hơn nên viết như vậy:
c1.address.city = c2.address!.city // ERROR: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Bây giờ bạn có thể cho tôi biết đó là vật thể nil
nào không?
Lần này mã đã được làm rõ ràng hơn cho bạn. Bạn có thể hợp lý hóa và nghĩ rằng có thể đó là address
tham số đã được mở ra một cách cưỡng bức.
Mã đầy đủ sẽ là:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c2 = GoodContact()
c1.address.city = c2.address!.city
c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
if let city = c2.address?.city { // safest approach. But that's not what I'm talking about here.
c1.address.city = city
}
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct GoodContact {
var address : Address?
}
struct Address {
var city : String
}
Lỗi EXC_BAD_INSTRUCTION
và fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
xuất hiện nhiều nhất khi bạn đã khai báo @IBOutlet
nhưng chưa kết nối với bảng phân cảnh .
Bạn cũng nên tìm hiểu về cách hoạt động của Tùy chọn , được đề cập trong các câu trả lời khác, nhưng đây là lần duy nhất chủ yếu xuất hiện với tôi.
Nếu bạn gặp lỗi này trong CollectionView, hãy thử tạo tệp CustomCell và tùy chỉnh xib.
thêm mã này trong ViewDidLoad () tại mainVC.
let nib = UINib(nibName: "CustomnibName", bundle: nil)
self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
Tôi đã gặp lỗi này trong khi tạo một segue từ bộ điều khiển chế độ xem bảng sang bộ điều khiển chế độ xem vì tôi đã quên chỉ định tên lớp tùy chỉnh cho bộ điều khiển chế độ xem trong bảng phân cảnh chính.
Một cái gì đó đơn giản nhưng đáng để kiểm tra nếu mọi thứ khác đều ổn