“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ì?

Aug 24 2015

Chương trình Swift của tôi đang gặp sự cố EXC_BAD_INSTRUCTIONvà 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

685 Hamish Aug 24 2015 at 02:10

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>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).none, trong đó .nonetươ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ển Intnhầ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ọn Stringsang một đoạn mã yêu cầu không tùy chọn String. 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: IntisValid: Bool) hoặc một giá trị sentinel được chỉ định (chẳng hạn như -1hoặ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 isValidhoặ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 Optionalgiải quyết những vấn đề này bằng cách giới thiệu một nilgiá 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ư anOptionalStringnilở đâ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 anOptionalIntchứa một giá trị, nó sẽ được mở ra và gán cho numberhằ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 wheremệ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 đó alà một loại tùy chọn và bcù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 achứa một giá trị, hãy mở nó ra. Nếu không thì hãy quay lại bthay 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 numberhằng số Intkiểu, sẽ chứa giá trị của anOptionalInt, nếu nó chứa một giá trị hoặc 0nế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 Footrườ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 fookhô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 foochứ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 niltrong 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 foonil. 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ì Voidtrê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ì foolà 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ở foochí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 foocó 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ì foobazlà tùy chọn, giá trị trả về từ quxsẽ luôn là tùy chọn bất kể quxchính nó có phải là tùy chọn hay không.

mapflatMap

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 mapflatMap. 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 maphàm cho nó - chúng ta có thể sử dụng stringByAppendingStringhàm để nối nó với một chuỗi khác.

stringByAppendingStringnhậ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 stringByAppendingStringto be used nếu anOptionalStringcó 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 anOptionalStringkhông có giá trị, mapsẽ trả về nil. Ví dụ:

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMaphoạ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 catchkhối.

Các errorhằng số mà bạn nhìn thấy trong catchkhố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ể errortự 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 trycá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

65 Sajjon Dec 19 2016 at 20:26

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 lethoặ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

40 DuncanC Feb 28 2016 at 21:37

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 footù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.

13 Tharzeez Nov 17 2017 at 23:15

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 nilChúng tôi phải tuân theo một giao thức được gọi ExpressibleByNilLiteralvà tuyệt vời nếu bạn đoán được nó, chỉ khuyến khích Optionalstuân theo ExpressibleByNilLiteralvà phù hợp với các kiểu khác.

ExpressibleByNilLiteralcó 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 nilchữ.

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 .

12 QiunCheng Jul 30 2016 at 14:18

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 ... elsev.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ế.

8 Wissa Apr 13 2018 at 21:06

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 đề.

7 Cristik Sep 19 2018 at 13:04

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:

  1. buộc phải mở:

    let user = someVariable!
    

    Nếu someVariablelà 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ằng someVariable?

    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
    }
    
  2. é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 Rectanglephiê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
    }
    
  3. 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 nametính bằng cách đặt nó thành nil, thì nó hoạt động như mong đợi, tuy nhiên nếu Userđược khởi tạo từ JSON thiếu namekhó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 đó

  4. 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ã.

  5. 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ộc nametính sẽ được nhập trong Swift dưới dạng String!(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ếu namelà 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.

2 Honey Nov 01 2018 at 11:39

Đâ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 nilcá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 nilnà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ố niltừ 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 addresskhô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ể nilnà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à addresstham 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
}
2 AleMohamad May 21 2019 at 00:34

Lỗi EXC_BAD_INSTRUCTIONfatal error: unexpectedly found nil while implicitly unwrapping an Optional valuexuất hiện nhiều nhất khi bạn đã khai báo @IBOutletnhư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.

2 ShigaSuresh Feb 13 2020 at 01:16

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")
MikeVolmar Sep 26 2019 at 22:11

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