Lỗi Fatal có ý nghĩa gì: Không ngờ tìm thấy con số không trong khi hủy bỏ giá trị Tùy chọn có nghĩa là gì?


415

Chương trình Swift của tôi bị lỗi EXC_BAD_INSTRUCTIONvà một trong những 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: Bất ngờ tìm thấy con số không trong khi hủy bỏ giá trị Tùy chọn

hoặc là

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


Bài đăng này nhằm thu thập câu trả lời cho các vấn đề "không tìm thấy khô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ó.


8
@RobertColumbia, đây là cặp Hỏi & Đáp về Wiki cộng đồng với tài liệu mở rộng về vấn đề này, tôi không nghĩ câu hỏi này nên bị đóng vì thiếu MCVE.
JAL

Tạo biến như thế này [var nameOfD daughter: String? ] và sử dụng biến đó như thế này [if let _ = nameOfD daughter {}] là cách tiếp cận tốt nhất.
iOS

Câu trả lời:


673

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, Optionallà 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 nilkhông giống như niltrong Objective-C. Trong Objective-C, nilkhô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.

đường dây bị rơi

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ư 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. 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 anOptionalIntchứa một giá trị, nó sẽ được mở ra và gán cho numberhằ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 wheremệ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 đó alà một loại tùy chọn và bcù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 acó chứa một giá trị, hãy mở khóa. Nếu không thì hãy quay lại bthay 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 numberhằng số Intloại, 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 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 Foothể hiện tùy chọn .

var foo : Foo?

Nếu chúng ta muốn gọi một phương thức foomà không trả về bất cứ điều gì, chúng ta chỉ cần làm:

foo?.doSomethingInteresting()

Nếu foochứ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 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ẽ 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ì Voidtrê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ì foolà 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 footự 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 foocó 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ì foobazlà tùy chọn, giá trị được trả về quxsẽ luôn là tùy chọn bất kể quxchính nó là tùy chọn.

mapflatMap

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

stringByAppendingStringcó 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 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ự 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 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ể 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 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 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


87
Cá nhân, tôi muốn cảm ơn bạn đã nỗ lực để viết tất cả những điều này. Tôi cảm thấy như điều này chắc chắn sẽ hữu ích cho người mới bắt đầu và các chuyên gia cũng như sử dụng swift.
Pranav Wadhwa

15
Tôi rất vui vì bạn thấy nó hữu ích. Câu trả lời này là wiki cộng đồng, vì vậy đây là sự hợp tác giữa nhiều người (7, cho đến nay)!
jtbandes

1
Lỗi này là không liên tục trong trường hợp của tôi. Bất kỳ đề xuất? Mã trong stackoverflow.com/questions/50933681/
Mạnh

1
Có lẽ đã đến lúc cập nhật câu trả lời này để sử dụng compactMap()thay vì flatMap().
Nicolas Miari

Vì vậy, tôi đoán giải pháp tốt nhất và ngắn nhất là "Toán tử hợp nhất Nil", phải không?
mehdigriche

65

TL; DR trả lời

Với rất ít 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 ngầm định tùy chọn (IUO) ( !)

Nói cách khác, thay vì sử dụng:
var nameOfDaughter: String?

Thay vì:
var nameOfDaughter: String!

Unwrap biến tùy chọn bằng cách sử dụng if lethoặcguard let

Hoặc là biến unrap 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 dự định ngắn gọn, để hiểu đầy đủ đọc câu trả lời được chấp nhận


Tài nguyên


40

Câu hỏi này xuất hiện TẤT CẢ THỜI GIAN trên 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 với.

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 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 để chỉ ra rằng nó không chứa giá trị. Tuy nhiên, nếu 0 là giá trị hợp lệ thì sao? Sau đó, bạn có thể sử dụng -1. Nếu -1 là giá trị hợp lệ thì sao? Và như thế.

Tùy chọn Swift 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 một dấu hỏi sau loại khi bạn khai báo một biến có nghĩa (loại x hoặc không có giá trị).

Một tùy chọn thực sự là một thùng chứa hơn chứa một biến của một loại nhất định hoặc không có gì.

Một tùy chọn cần phải được "mở khóa" để lấy giá trị bên trong.

Các "!" toán tử là một toán tử "buộc unrap". Nó nói "hãy tin tôi. Tôi biết những gì tôi đang làm. 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ụp đổ.

Trừ khi bạn thực sự làm biết những gì bạn đang làm, tránh những "!" lực lượng điều hành Unrap. Nó có lẽ là nguồn tai nạn lớn nhất cho các lập trình viên Swift mới bắt đầu.

Làm thế nào để đối phó với các tùy chọn:

Có rất nhiều cách khác để đối phó với các tùy chọn an toàn hơn. Đây là một số (không phải là một danh sách đầy đủ)

Bạn có thể sử dụng "ràng buộc tùy chọn" hoặc "nếu để" nói "nếu tùy chọn này chứa giá trị, lưu giá trị đó vào biến mới, không tùy chọn. Nếu tùy chọn không chứa giá trị, hãy bỏ qua phần thân của câu lệnh if này. ".

Dưới đâ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 bạn xác định khi bạn sử dụng tùy chọn chỉ tồn tại (chỉ "trong phạm vi") trong phần thân của câu lệnh if.

Thay phiên, bạn có thể sử dụng một 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à không:

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 bảo vệ đã được thêm vào trong Swift 2. Guard cho phép bạn bảo vệ "con đường 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 dẫn đến 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ử hợp nhất nil". Nó có dạng "tùy chọn_var ?? thay_val". Nó trả về một biến không tùy chọn có cùng loại với dữ liệu chứa trong tùy chọn. Nếu tùy chọn chứa nil, nó sẽ 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ư thế này:

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 nói chung, một trong những kỹ thuật khác ở trên là sạch hơn.

BIÊN TẬP:

Một điều khác, một chút tinh tế hơn với các tùy chọn là "tùy chọn không được bao bọc. 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à một tùy chọn, nhưng bạn không cần phải mở khóa để tham khảo nó. Điều đó có nghĩa là bất cứ khi nào bạn cố gắng tham khảo foo, bạn sẽ gặp sự cố nếu không.

Mã này:

var foo: String!


let upperFoo = foo.capitalizedString

Sẽ sụp đổ khi tham chiếu đến tài sản CapitalizedString của foo mặc dù chúng tôi không ép buộc foo. bản in có vẻ tốt, nhưng nó không phải là.

Vì vậy, bạn muốn thực sự cẩn thận với các tùy chọn ngầm định. (và thậm chí có thể tránh chúng hoàn toàn cho đến khi bạn có một sự 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ả vờ "!" nhân vật không phải là một phần của ngôn ngữ. Nó có khả năng khiến bạn gặp rắc rối.


7
Bạn nên xem xét làm cho nó một wiki công cộng.
vacawama

3
Chỉnh sửa câu trả lời của bạn và bên dưới hộp chỉnh sửa bên phải là hộp kiểm wiki cộng đồng . Bạn sẽ không còn nhận được câu trả lời cho câu trả lời nữa, nhưng tôi biết dù sao đây cũng không phải là một đại diện trắng trợn.
vacawama

6
Cho rằng câu hỏi này được hỏi hàng triệu lần trên trang web, nếu tôi dành thời gian để đăng câu hỏi tự trả lời như câu trả lời chính tắc cho câu hỏi, tôi hy vọng rằng câu trả lời hoàn chỉnh hơn nhiều so với câu hỏi này. Không có gì về guardmệnh đề. Không có gì về việc sử dụng if varmà chỉ là một cấu trúc hợp lệ như if let. Không có gì về wherecác mệnh đề mà tôi cảm thấy đáng được đề cập khi chúng ta nói về if letràng buộc (nó loại bỏ toàn bộ một lớp lồng trong nhiều trường hợp). Không có gì về chuỗi tùy chọn.
nhgrif

6
Và khi bạn đề cập đến các tùy chọn không được bao bọc hoàn toàn, bạn thậm chí không đề cập rằng bạn có thể sử dụng tất cả các thủ thuật tùy chọn đã nói ở trên để mở khóa một cách an toàn các tùy chọn không được bao bọc. Chúng tôi cũng không bao gồm việc hủy ghép nhiều tùy chọn trong cùng một mệnh đề.
nhgrif

1
@nhgrif, tôi đã nói "Bạn cũng có thể sử dụng xử lý lỗi thử / bắt hoặc bảo vệ, nhưng nói chung, một trong những kỹ thuật khác ở trên là sạch hơn." Bạn chắc chắn có một số điểm tốt. Đây là một chủ đề khá lớn. Làm thế nào về việc đóng góp câu trả lời của riêng bạn bao gồm những điều tôi không làm hơn là đưa ra những bình luận bắn tỉa? Bằng cách đó, câu hỏi và câu trả lời của nó trở thành một tài sản tốt hơn cho trang web.
Duncan C

13

Vì các câu trả lời trên giải thích rõ ràng cách chơi 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ự trong 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 có gì ngoài một bảng liệt kê với hai trường hợp, nghĩa là

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Vì vậy, để gán một con số không cho biến 'i' của chúng tôi. Chúng tôi có thể làm var i = Optional<Int>.none hoặc để gán 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ự thiếu vắng giá trị. Và để tạo một cá thể được khởi tạo với nilChúng tôi phải tuân thủ một giao thức được gọi ExpressibleByNilLiteralvà tuyệt vời nếu bạn đoán nó, chỉ Optionalstuân thủ ExpressibleByNilLiteralvà tuân thủ các loại khác là không được khuyến khích.

ExpressibleByNilLiteralcó một phương thức duy nhất được gọi là init(nilLiteral:)khởi tạo instace bằng nil. Bạn thường không gọi phương thức này và theo tài liệu swift, không nên 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 loại Tùy chọn bằng nilchữ.

Ngay cả bản thân tôi cũng phải quấn lấy (không có ý định chơi chữ) đầu của tôi xung quanh Tùy chọn: D Happy Swfting All .


12

Đầu tiên, bạn nên biết giá trị Tùy chọn là gì. Bạn có thể bước đến Ngôn ngữ lập trình Swift để biết 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 cái là giá trị đầy đủ, và cái kia là giá trị không. Vì vậy, trước khi bạn thực hiện một giá trị tùy chọn, bạn nên kiểm tra xem nó ở trạng thái nào.

Bạn có thể sử dụng if let ...hoặc guard let ... elsenhư vậy.

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 thực hiện, bạn cũng có thể sử dụng var buildingName = buildingName ?? "buildingName"thay thế.


8

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 phương thức 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 đầu ra của 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 bất cứ ai có vấn đề tương tự như tôi thấy câu trả lời được đánh dấu là nguồn lực lớn để hiểu về các lựa chọn và cách họ làm việc nhưng không trực tiếp giải quyết vấn đề.


Chỉ cần đừng quên đề cập đến nơi cần gọi updateViewtrong bộ điều khiển đích;)
Ahmad F

@AHmadF điểm tốt. Tuy nhiên, việc gọi updateViewbộ điều khiển đích là không cần thiết trong trường hợp này vì tôi đang sử dụng namebiến để đặt nameLabel.texttrong viewDidLoad. Nhưng nếu chúng ta thực hiện nhiều thiết lập thì chắc chắn sẽ tốt hơn khi tạo một hàm khác làm như vậy và gọi nó từ viewDidLoadthay thế.
Wissa

7

Về cơ bản, bạn đã cố gắng sử dụng một giá trị không ở những nơi Swift chỉ cho phép những giá trị không, 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ị nào ở đó, do đó cho phép ứng dụng của bạn biên dịch.

Có một số tình huống dẫn đến loại lỗi nghiêm trọng này:

  1. buộc phải hủy bỏ:

    let user = someVariable!

    Nếu someVariablekhông, thì bạn sẽ gặp sự cố. Bằng cách thực hiện một lực lượng, bạn đã chuyển trách nhiệm kiểm tra con số không từ trình biên dịch sang bạn, về cơ bản bằng cách thực hiện một thao tác cưỡng bức mà bạn đảm bảo với trình biên dịch rằng bạn sẽ không bao giờ có giá trị nào ở đó. Và đoán xem điều gì sẽ xảy ra nếu bằng cách nào đó một giá trị không kết thúc bằng someVariable?

    Giải pháp? Sử dụng ràng buộc 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

    Ở đây bằng cách ép buộc, bạn nói với trình biên dịch không còn lo lắng nữa, vì bạn sẽ luôn có một Rectangleví dụ ở đó. Và miễn là giữ được, 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 lưu hành các giá trị không phải hình chữ nhật.

    Giải pháp? Sử dụng ràng buộc 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. Hoàn toàn không có tùy chọ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ó nil, thì nó hoạt động như mong đợi, tuy nhiên nếu Userđược khởi tạo từ JSON thiếuname khóa, thì bạn sẽ gặp lỗi nghiêm trọng khi thử 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 tài sản sẽ luôn có giá trị không bằng thời gian cần 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 trình biên dịch giúp bạn bằng cách nói các đường dẫn mã bạn đã bỏ lỡ đưa ra một giá trị cho thuộc tính đó

  4. Các cửa hàng không đượ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 kịch bản # 3. Về cơ bản, bạn có một số lớp nạp 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ẽ sập ngay khi bạn muốn sử dụng ổ cắm. Giải pháp? Hãy chắc chắn rằng tất cả các cửa hàng được kết nối. Hoặc sử dụng ?toán tử trên chúng : emailTextField?.text = "my@email.com". Hoặc khai báo ổ cắm 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 mở khóa toàn bộ mã.

  5. Các giá trị đến từ Objective-C và không có chú thích vô hiệu. 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 vô hiệu nào được chỉ định (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 - hoàn toàn không được tùy chọn). Ngay khi một số mã nhanh chóng muốn sử dụng giá trị, nó sẽ sập nếu namekhông.

    Giải pháp? Thêm chú thích vô hiệu vào mã Objective-C của bạn. Mặc dù vậy, hãy cẩn thận, trình biên dịch Objective-C hơi dễ chấp nhận khi không có giá trị, bạn có thể kết thúc bằng các giá trị không, ngay cả khi bạn đánh dấu rõ ràng là nonnull.


2

Đây là một nhận xét quan trọng hơn và tại sao các tùy chọn không được bao bọc hoàn toàn có thể bị đánh lừa 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 con số không trong khi hủy bỏ giá trị Tùy chọn

Bạn có thể cho tôi biết đối tượng là nilgì?

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
}

Câu chuyện dài bằng cách sử dụng var address : Address!bạn đang che giấu khả năng một biến có thể niltừ các độc giả khác. Và khi nó gặp sự cố, bạn sẽ như "cái quái gì vậy?!address không phải là một lựa chọn, vậy tại sao tôi lại gặp nạn?!.

Do đó, tốt hơn là 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à đối tượng nilnào không?

Lần này mã đã được làm rõ hơn cho bạn. Bạn có thể hợp lý hóa và nghĩ rằng có khả năng đó là addresstham số mạnh mẽ chưa được mở.

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

Các 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 @IBOutlet, nhưng không được 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 thức 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 xuất hiện với tôi.


Không phải @IBOutletnguyên nhân gây ra lỗi này có lỗi nghiêm trọng: Không tìm thấy con số không trong khi hoàn toàn hủy bỏ phiên bản giá trị Tùy chọn của lỗi?
pkamb

1
Đúng. Có lẽ khi tôi gửi câu trả lời đó là điều tôi muốn nói và tôi đã sao chép thông báo lỗi nghiêm trọng đầu tiên. Câu trả lời từ Hamish có vẻ rất đầy đủ về điều này.
Ale Mohamad

2

Nếu bạn gặp lỗi này trong CollectionView, hãy thử tạo tệp CustomCell và Custom xib.

thêm mã này trong ViewDidLoad () tại mainVC.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

0

Tôi đã gặp phải lỗi này trong khi tạo một segue từ bộ điều khiển xem bảng sang bộ điều khiển xem vì tôi đã quên chỉ định tên lớp tùy chỉnh cho bộ điều khiển xem trong bảng phân cảnh chính.

Một cái gì đó đơn giản đáng để kiểm tra nếu tất cả những thứ khác có vẻ ổn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.