简体   繁体   English

“致命错误:在展开可选值时意外发现 nil”是什么意思?

[英]What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?

My Swift program is crashing with EXC_BAD_INSTRUCTION and one of the following similar errors.我的 Swift 程序因EXC_BAD_INSTRUCTION和以下类似错误之一而崩溃。 What does this error mean, and how do I fix it?这个错误是什么意思,我该如何解决?

Fatal error: Unexpectedly found nil while unwrapping an Optional value致命错误:在展开可选值时意外发现 nil

or要么

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value致命错误:在隐式展开可选值时意外发现 nil


This post is intended to collect answers to "unexpectedly found nil" issues, so that they are not scattered and hard to find.本帖意在收集“意外发现nil”问题的答案,不至于零散难找。 Feel free to add your own answer or edit the existing wiki answer.随意添加您自己的答案或编辑现有的 wiki 答案。

Background: What's an Optional?背景:什么是可选的?

In Swift, Optional<Wrapped> is an option type : it can contain any value from the original ("Wrapped") type, or no value at all (the special value nil ).在 Swift 中, Optional<Wrapped>是一个选项类型:它可以包含原始(“Wrapped”)类型中的任何值,或者根本不包含任何值(特殊值nil )。 An optional value must be unwrapped before it can be used.可选值必须先解包,然后才能使用。

Optional is a generic type , which means that Optional<Int> and Optional<String> are distinct types — the type inside <> is called the Wrapped type. Optional 是泛型类型,这意味着Optional<Int>Optional<String>是不同的类型 - <>中的类型称为 Wrapped 类型。 Under the hood, an Optional is an enum with two cases: .some(Wrapped) and .none , where .none is equivalent to nil .在底层, Optional 是一个有两种情况的枚举.some(Wrapped).none ,其中.none相当于nil

Optionals can be declared using the named type Optional<T> , or (most commonly) as a shorthand with a ? Optional 可以使用命名类型Optional<T>来声明,或者(最常见的)作为带有? suffix.后缀。

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

Optionals are a simple yet powerful tool to express your assumptions while writing code. Optionals 是一个简单而强大的工具,可以在编写代码时表达您的假设。 The compiler can use this information to prevent you from making mistakes.编译器可以使用此信息来防止您犯错误。 From The Swift Programming Language :来自Swift 编程语言

Swift is a type-safe language, which means the language helps you to be clear about the types of values your code can work with. Swift 是一种类型安全的语言,这意味着该语言可以帮助您清楚代码可以使用的值的类型。 If part of your code requires a String , type safety prevents you from passing it an Int by mistake.如果您的部分代码需要String ,则类型安全会防止您错误地将Int传递给它。 Likewise, type safety prevents you from accidentally passing an optional String to a piece of code that requires a non-optional String .同样,类型安全可防止您意外将可选String String一段代码。 Type safety helps you catch and fix errors as early as possible in the development process.类型安全可帮助您在开发过程中尽早发现并修复错误。

Some other programming languages also have generic option types : for example, Maybe in Haskell, option in Rust, and optional in C++17.其他一些编程语言也有通用选项类型:例如,Haskell 中的Maybe 、Rust 中的option和 C++17 中的optional

In programming languages without option types, a particular "sentinel" value is often used to indicate the absence of a valid value.没有选项类型的编程语言中,通常使用特定的“哨兵”值来指示不存在有效值。 In Objective-C, for example, nil (the null pointer ) represents the lack of an object.例如,在 Objective-C 中, nil空指针)表示缺少对象。 For primitive types such as int , a null pointer can't be used, so you would need either a separate variable (such as value: Int and isValid: Bool ) or a designated sentinel value (such as -1 or INT_MIN ).对于int等原始类型,不能使用空指针,因此您需要一个单独的变量(例如value: IntisValid: Bool )或指定的标记值(例如-1INT_MIN )。 These approaches are error-prone because it's easy to forget to check isValid or to check for the sentinel value.这些方法容易出错,因为很容易忘记检查isValid或检查哨兵值。 Also, if a particular value is chosen as the sentinel, that means it can no longer be treated as a valid value.此外,如果选择特定值作为哨兵,这意味着它不能再被视为有效值

Option types such as Swift's Optional solve these problems by introducing a special, separate nil value (so you don't have to designate a sentinel value), and by leveraging the strong type system so the compiler can help you remember to check for nil when necessary.诸如 Swift 的Optional之类的选项类型通过引入一个特殊的、单独的nil值(因此您不必指定标记值)以及利用强类型系统来帮助您记住在何时检查 nil 来解决这些问题必要的。


Why did I get “ Fatal error: Unexpectedly found nil while unwrapping an Optional value ”?为什么我会收到“ Fatal error: Unexpectedly found nil while unwrapping an Optional value ”?

In order to access an optional's value (if it has one at all), you need to unwrap it.为了访问一个可选的值(如果它有一个),你需要打开它。 An optional value can be unwrapped safely or forcibly.可选值可以安全或强制解包。 If you force-unwrap an optional, and it didn't have a value, your program will crash with the above message.如果你强制解包一个可选项,但它没有值,你的程序将因上述消息而崩溃。

Xcode will show you the crash by highlighting a line of code. Xcode 将通过突出显示一行代码向您显示崩溃。 The problem occurs on this line.问题出现在这条线上。

崩溃的线

This crash can occur with two different kinds of force-unwrap:两种不同类型的强制展开可能会发生此崩溃:

1. Explicit Force Unwrapping 1. 显式强制展开

This is done with the !这是用! operator on an optional.可选的运算符。 For example:例如:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Fatal error: Unexpectedly found nil while unwrapping an Optional value致命错误:在展开可选值时意外发现 nil

As anOptionalString is nil here, you will get a crash on the line where you force unwrap it.由于此处的anOptionalString nil ,因此您将在强制展开它的行上发生崩溃。

2. Implicitly Unwrapped Optionals 2. 隐式解包选项

These are defined with a !这些是用! , rather than a ? , 而不是? after the type.类型之后。

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

These optionals are assumed to contain a value.假定这些选项包含一个值。 Therefore whenever you access an implicitly unwrapped optional, it will automatically be force unwrapped for you.因此,每当您访问隐式展开的可选选项时,它都会自动为您强制展开。 If it doesn't contain a value, it will crash.如果它不包含值,它将崩溃。

print(optionalDouble) // <- CRASH

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value致命错误:在隐式展开可选值时意外发现 nil

In order to work out which variable caused the crash, you can hold while clicking to show the definition, where you might find the optional type.为了找出导致崩溃的变量,您可以按住 ,同时单击以显示定义,您可能会在其中找到可选类型。

IBOutlets, in particular, are usually implicitly unwrapped optionals.尤其是 IBOutlets,通常是隐式展开的可选项。 This is because your xib or storyboard will link up the outlets at runtime, after initialization.这是因为您的 xib 或故事板将在初始化在运行时链接出口。 You should therefore ensure that you're not accessing outlets before they're loaded in. You also should check that the connections are correct in your storyboard/xib file, otherwise the values will be nil at runtime, and therefore crash when they are implicitly unwrapped.因此,您应该确保在加载出口之前没有访问它们。您还应该检查故事板/xib 文件中的连接是否正确,否则这些值在运行时将为nil ,因此当它们隐含时会​​崩溃展开。 When fixing connections, try deleting the lines of code that define your outlets, then reconnect them.修复连接时,请尝试删除定义插座的代码行,然后重新连接它们。


When should I ever force unwrap an Optional?我什么时候应该强制打开 Optional?

Explicit Force Unwrapping显式强制展开

As a general rule, you should never explicitly force unwrap an optional with the !作为一般规则,您永远不应使用! operator.操作员。 There may be cases where using !可能存在使用! is acceptable – but you should only ever be using it if you are 100% sure that the optional contains a value.是可以接受的——但只有当你 100% 确定可选项包含一个值时,你才应该使用它。

While there may be an occasion where you can use force unwrapping, as you know for a fact that an optional contains a value – there is not a single place where you cannot safely unwrap that optional instead.虽然在某些情况下您可以使用强制解包,但正如您所知道的,一个可选项包含一个值的事实- 没有一个地方可以安全地解包该可选项。

Implicitly Unwrapped Optionals隐式展开的选项

These variables are designed so that you can defer their assignment until later in your code.这些变量的设计是为了让您可以将它们的分配推迟到代码的后面。 It is your responsibility to ensure they have a value before you access them.有责任在访问它们之前确保它们具有价值。 However, because they involve force unwrapping, they are still inherently unsafe – as they assume your value is non-nil, even though assigning nil is valid.然而,因为它们涉及强制展开,它们本质上仍然是不安全的——因为它们假设你的值是非 nil,即使分配 nil 是有效的。

You should only be using implicitly unwrapped optionals as a last resort .作为最后的手段,您应该只使用隐式展开的选项。 If you can use a lazy variable , or provide a default value for a variable – you should do so instead of using an implicitly unwrapped optional.如果您可以使用惰性变量,或者为变量提供默认值- 您应该这样做,而不是使用隐式展开的可选项。

However, there are a few scenarios where implicitly unwrapped optionals are beneficial , and you are still able to use various ways of safely unwrapping them as listed below – but you should always use them with due caution.但是,在某些情况下,隐式展开的选项是有益的,您仍然可以使用下面列出的各种安全展开它们的方法 - 但您应该始终谨慎使用它们。


How can I safely deal with Optionals?如何安全地处理 Optionals?

The simplest way to check whether an optional contains a value, is to compare it to nil .检查可选项是否包含值的最简单方法是将其与nil进行比较。

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

However, 99.9% of the time when working with optionals, you'll actually want to access the value it contains, if it contains one at all.但是,99.9% 的情况下,在使用可选项时,您实际上会想要访问它包含的值,如果它包含一个值的话。 To do this, you can use Optional Binding .为此,您可以使用可选绑定

Optional Binding可选绑定

Optional Binding allows you to check if an optional contains a value – and allows you to assign the unwrapped value to a new variable or constant. Optional Binding 允许您检查一个 optional 是否包含一个值——并允许您将展开的值分配给一个新的变量或常量。 It uses the syntax if let x = anOptional {...} or if var x = anOptional {...} , depending if you need to modify the value of the new variable after binding it.它使用语法if let x = anOptional {...}if var x = anOptional {...} ,具体取决于您是否需要在绑定后修改新变量的值。

For example:例如:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

What this does is first check that the optional contains a value.这样做是首先检查可选项是否包含一个值。 If it does , then the 'unwrapped' value is assigned to a new variable ( number ) – which you can then freely use as if it were non-optional.如果是这样,则将“展开”值分配给一个新变量( number )——然后您可以自由使用它,就好像它是非可选的一样。 If the optional doesn't contain a value, then the else clause will be invoked, as you would expect.如果可选项包含值,则将调用 else 子句,如您所料。

What's neat about optional binding, is you can unwrap multiple optionals at the same time.可选绑定的巧妙之处在于,您可以同时解开多个可选绑定。 You can just separate the statements with a comma.您可以用逗号分隔语句。 The statement will succeed if all the optionals were unwrapped.如果所有选项都被解包,则该语句将成功。

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")
}

Another neat trick is that you can also use commas to check for a certain condition on the value, after unwrapping it.另一个巧妙的技巧是,您还可以在展开后使用逗号检查值的某个条件。

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

The only catch with using optional binding within an if statement, is that you can only access the unwrapped value from within the scope of the statement.在 if 语句中使用可选绑定的唯一问题是,您只能从语句范围内访问未包装的值。 If you need access to the value from outside of the scope of the statement, you can use a guard statement .如果您需要从语句范围之外访问值,则可以使用保护语句

A guard statement allows you to define a condition for success – and the current scope will only continue executing if that condition is met.一个保护语句允许你定义一个成功的条件——并且当前作用域只有在满足该条件时才会继续执行。 They are defined with the syntax guard condition else {...} .它们使用语法guard condition else {...}定义。

So, to use them with an optional binding, you can do this:因此,要将它们与可选绑定一起使用,您可以这样做:

guard let number = anOptionalInt else {
    return
}

(Note that within the guard body, you must use one of the control transfer statements in order to exit the scope of the currently executing code). (请注意,在保护主体内,您必须使用控制转移语句之一才能退出当前执行代码的范围)。

If anOptionalInt contains a value, it will be unwrapped and assigned to the new number constant.如果anOptionalInt包含一个值,它将被解包并分配给新的number常量。 The code after the guard will then continue executing.守卫之后的代码将继续执行。 If it doesn't contain a value – the guard will execute the code within the brackets, which will lead to transfer of control, so that the code immediately after will not be executed.如果它不包含值 - 守卫将执行括号内的代码,这将导致控制权转移,因此紧随其后的代码将不会被执行。

The real neat thing about guard statements is the unwrapped value is now available to use in code that follows the statement (as we know that future code can only execute if the optional has a value).关于保护语句的真正巧妙之处在于,解包后的值现在可以在语句后面的代码中使用(因为我们知道未来的代码只有在可选项有值的情况下才能执行)。 This is a great for eliminating 'pyramids of doom' created by nesting multiple if statements.这对于消除嵌套多个 if 语句所产生的“厄运金字塔”非常有用。

For example:例如:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

Guards also support the same neat tricks that the if statement supported, such as unwrapping multiple optionals at the same time and using the where clause.守卫还支持 if 语句所支持的同样巧妙的技巧,例如同时解开多个可选项并使用where子句。

Whether you use an if or guard statement completely depends on whether any future code requires the optional to contain a value.是否使用 if 或 guard 语句完全取决于任何未来的代码是否需要可选来包含一个值。

Nil Coalescing Operator无合并运算符

The Nil Coalescing Operator is a nifty shorthand version of the ternary conditional operator , primarily designed to convert optionals to non-optionals. Nil Coalescing Operator三元条件运算符的一个漂亮的简写版本,主要用于将可选项转换为非可选项。 It has the syntax a ?? b它的语法a ?? b a ?? b , where a is an optional type and b is the same type as a (although usually non-optional). a ?? b ,其中a是可选类型, b是与 a 相同a类型(尽管通常是非可选的)。

It essentially lets you say “If a contains a value, unwrap it.它本质上让你说“如果a包含一个值,请打开它。 If it doesn't then return b instead”.如果不是,则返回b ”。 For example, you could use it like this:例如,您可以像这样使用它:

let number = anOptionalInt ?? 0

This will define a number constant of Int type, that will either contain the value of anOptionalInt , if it contains a value, or 0 otherwise.这将定义一个Int类型的number常量,如果它包含一个值,它将包含anOptionalInt的值,否则0

It's just shorthand for:它只是以下的简写:

let number = anOptionalInt != nil ? anOptionalInt! : 0

Optional Chaining可选链

You can use Optional Chaining in order to call a method or access a property on an optional.您可以使用可选链接来调用方法或访问可选的属性。 This is simply done by suffixing the variable name with a ?这只是通过在变量名后加一个?来完成的。 when using it.使用时。

For example, say we have a variable foo , of type an optional Foo instance.例如,假设我们有一个变量foo ,它的类型是一个可选的Foo实例。

var foo : Foo?

If we wanted to call a method on foo that doesn't return anything, we can simply do:如果我们想在foo上调用一个不返回任何内容的方法,我们可以简单地这样做:

foo?.doSomethingInteresting()

If foo contains a value, this method will be called on it.如果foo包含一个值,则会在其上调用此方法。 If it doesn't, nothing bad will happen – the code will simply continue executing.如果不这样做,就不会发生任何不好的事情——代码将继续执行。

(This is similar behaviour to sending messages to nil in Objective-C) (这类似于在 Objective-C 中向nil发送消息的行为)

This can therefore also be used to set properties as well as call methods.因此,这也可以用于设置属性以及调用方法。 For example:例如:

foo?.bar = Bar()

Again, nothing bad will happen here if foo is nil .同样,如果foonil ,这里不会发生任何不好的事情。 Your code will simply continue executing.您的代码将继续执行。

Another neat trick that optional chaining lets you do is check whether setting a property or calling a method was successful.可选链让你做的另一个巧妙的技巧是检查设置属性或调用方法是否成功。 You can do this by comparing the return value to nil .您可以通过将返回值与nil进行比较来做到这一点。

(This is because an optional value will return Void? rather than Void on a method that doesn't return anything) (这是因为可选值将返回Void?而不是Void在不返回任何内容的方法上)

For example:例如:

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

However, things become a little bit more tricky when trying to access properties or call methods that return a value.但是,当尝试访问属性或调用返回值的方法时,事情变得有点棘手。 Because foo is optional, anything returned from it will also be optional.因为foo是可选的,所以从它返回的任何东西也是可选的。 To deal with this, you can either unwrap the optionals that get returned using one of the above methods – or unwrap foo itself before accessing methods or calling methods that return values.为了解决这个问题,您可以解开使用上述方法之一返回的可选项——或者在访问方法或调用返回值的方法之前解开foo本身。

Also, as the name suggests, you can 'chain' these statements together.此外,顾名思义,您可以将这些语句“链接”在一起。 This means that if foo has an optional property baz , which has a property qux – you could write the following:这意味着如果foo有一个可选属性baz ,它有一个属性qux ——你可以写如下:

let optionalQux = foo?.baz?.qux

Again, because foo and baz are optional, the value returned from qux will always be an optional regardless of whether qux itself is optional.同样,因为foobaz是可选的,所以从qux返回的值将始终是可选的,无论qux本身是否是可选的。

map and flatMap mapflatMap地图

An often underused feature with optionals is the ability to use the map and flatMap functions.一个经常未被充分利用的可选特性是使用mapflatMap函数的能力。 These allow you to apply non-optional transforms to optional variables.这些允许您将非可选转换应用于可选变量。 If an optional has a value, you can apply a given transformation to it.如果可选项具有值,则可以对其应用给定的转换。 If it doesn't have a value, it will remain nil .如果它没有值,它将保持为nil

For example, let's say you have an optional string:例如,假设您有一个可选字符串:

let anOptionalString:String?

By applying the map function to it – we can use the stringByAppendingString function in order to concatenate it to another string.通过对它应用map函数——我们可以使用stringByAppendingString函数来将它连接到另一个字符串。

Because stringByAppendingString takes a non-optional string argument, we cannot input our optional string directly.因为stringByAppendingString需要一个非可选的字符串参数,所以我们不能直接输入我们的可选字符串。 However, by using map , we can use allow stringByAppendingString to be used if anOptionalString has a value.但是,通过使用map ,如果anOptionalString有值,我们可以使用 allow stringByAppendingString

For example:例如:

var anOptionalString:String? = "bar"

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

print(anOptionalString) // Optional("foobar")

However, if anOptionalString doesn't have a value, map will return nil .但是,如果anOptionalString没有值,则map将返回nil For example:例如:

var anOptionalString:String?

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

print(anOptionalString) // nil

flatMap works similarly to map , except it allows you to return another optional from within the closure body. flatMap的工作方式类似于map ,除了它允许您从闭包体内返回另一个可选值。 This means you can input an optional into a process that requires a non-optional input, but can output an optional itself.这意味着您可以将可选输入到需要非可选输入的流程中,但可以输出可选本身。

try!

Swift's error handling system can be safely used with Do-Try-Catch : Swift 的错误处理系统可以安全地与Do-Try-Catch一起使用:

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

If someThrowingFunc() throws an error, the error will be safely caught in the catch block.如果someThrowingFunc()抛出错误,错误将被安全地捕获在catch块中。

The error constant you see in the catch block has not been declared by us - it's automatically generated by catch .你在catch块中看到的error常量没有被我们声明——它是由catch自动生成的。

You can also declare error yourself, it has the advantage of being able to cast it to a useful format, for example:您也可以自己声明error ,它的优点是能够将其转换为有用的格式,例如:

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

Using try this way is the proper way to try, catch and handle errors coming from throwing functions.以这种方式使用try是尝试、捕获和处理来自抛出函数的错误的正确方法。

There's also try?也有try? which absorbs the error:它吸收了错误:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

But Swift's error handling system also provides a way to "force try" with try!但是 Swift 的错误处理系统也提供了一种使用try! :

let result = try! someThrowingFunc()

The concepts explained in this post also apply here: if an error is thrown, the application will crash.这篇文章中解释的概念也适用于此:如果抛出错误,应用程序将崩溃。

You should only ever use try!你应该只使用try! if you can prove that its result will never fail in your context - and this is very rare.如果你能证明它的结果在你的上下文中永远不会失败——这是非常罕见的。

Most of the time you will use the complete Do-Try-Catch system - and the optional one, try?大多数时候,您将使用完整的 Do-Try-Catch 系统 - 以及可选的, try? , in the rare cases where handling the error is not important. ,在处理错误并不重要的极少数情况下。


Resources资源

TL;DR answer TL;DR 答案

With very few exceptions , this rule is golden:除了极少数例外,这条规则是黄金法则:

Avoid use of !避免使用!

Declare variable optional ( ? ), not implicitly unwrapped optionals (IUO) ( ! )声明变量可选( ? ),而不是隐式展开的可选(IUO)( !

In other words, rather use:换句话说,宁可使用:
var nameOfDaughter: String?

Instead of:代替:
var nameOfDaughter: String!

Unwrap optional variable using if let or guard let使用if letguard let展开可选变量

Either unwrap variable like this:像这样解开变量:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

Or like this:或者像这样:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

This answer was intended to be concise, for full comprehension read accepted answer此答案旨在简明扼要,以便全面理解阅读接受的答案


Resources资源

This question comes up ALL THE TIME on SO.这个问题总是在 SO 上出现。 It's one of the first things that new Swift developers struggle with.这是新的 Swift 开发人员首先要解决的问题之一。

Background:背景:

Swift uses the concept of "Optionals" to deal with values that could contain a value, or not. Swift 使用“Optionals”的概念来处理可能包含或不包含值的值。 In other languages like C, you might store a value of 0 in a variable to indicate that it contains no value.在其他语言(如 C)中,您可能会将值 0 存储在变量中以指示它不包含任何值。 However, what if 0 is a valid value?但是,如果 0 是有效值怎么办? Then you might use -1.然后你可以使用-1。 What if -1 is a valid value?如果 -1 是有效值怎么办? And so on.等等。

Swift optionals let you set up a variable of any type to contain either a valid value, or no value. Swift 可选项让您可以设置任何类型的变量以包含有效值或不包含值。

You put a question mark after the type when you declare a variable to mean (type x, or no value).当您将变量声明为表示(类型 x 或无值)时,您在类型后放置一个问号。

An optional is actually a container than contains either a variable of a given type, or nothing.可选项实际上是一个容器,它要么包含给定类型的变量,要么什么都不包含。

An optional needs to be "unwrapped" in order to fetch the value inside.一个可选的需要“解包”才能获取里面的值。

The "!"这 ”!” operator is a "force unwrap" operator.运算符是“强制展开”运算符。 It says "trust me. I know what I am doing. I guarantee that when this code runs, the variable will not contain nil."它说“相信我。我知道我在做什么。我保证当这段代码运行时,变量不会包含 nil。” If you are wrong, you crash.如果你错了,你就会崩溃。

Unless you really do know what you are doing, avoid the "!"除非您真的知道自己在做什么,否则请避免使用“!” force unwrap operator.强制展开运算符。 It is probably the largest source of crashes for beginning Swift programmers.对于 Swift 初学者来说,这可能是最大的崩溃源。

How to deal with optionals:如何处理选项:

There are lots of other ways of dealing with optionals that are safer.还有许多其他更安全的处理选项的方法。 Here are some (not an exhaustive list)这里有一些(不是详尽的列表)

You can use "optional binding" or "if let" to say "if this optional contains a value, save that value into a new, non-optional variable. If the optional does not contain a value, skip the body of this if statement".您可以使用“可选绑定”或“if let”来表示“如果此可选包含值,则将该值保存到新的非可选变量中。如果可选不包含值,则跳过此 if 语句的主体”。

Here is an example of optional binding with our foo optional:这是与我们的foo可选绑定的可选示例:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

Note that the variable you define when you use optional biding only exists (is only "in scope") in the body of the if statement.请注意,您在使用可选出价时定义的变量仅存在于 if 语句的主体中(仅“在范围内”)。

Alternately, you could use a guard statement, which lets you exit your function if the variable is nil:或者,您可以使用保护语句,如果变量为 nil,您可以退出函数:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

Guard statements were added in Swift 2. Guard lets you preserve the "golden path" through your code, and avoid ever-increasing levels of nested ifs that sometimes result from using "if let" optional binding. Guard 语句是在 Swift 2 中添加的。Guard 让您可以通过代码保留“黄金路径”,并避免有时由于使用“if let”可选绑定而导致的嵌套 if 级别不断增加。

There is also a construct called the "nil coalescing operator".还有一个称为“零合并运算符”的构造。 It takes the form "optional_var ?? replacement_val".它采用“optional_var ?? replacement_val”的形式。 It returns a non-optional variable with the same type as the data contained in the optional.它返回一个与可选中包含的数据具有相同类型的非可选变量。 If the optional contains nil, it returns the value of the expression after the "??"如果可选项包含 nil,则返回 "??" 后面的表达式的值symbol.象征。

So you could use code like this:所以你可以使用这样的代码:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

You could also use try/catch or guard error handling, but generally one of the other techniques above is cleaner.您也可以使用 try/catch 或保护错误处理,但通常上述其他技术之一更干净。

EDIT:编辑:

Another, slightly more subtle gotcha with optionals is "implicitly unwrapped optionals. When we declare foo, we could say:另一个更微妙的可选选项是“隐式展开的选项。当我们声明 foo 时,我们可以说:

var foo: String!

In that case foo is still an optional, but you don't have to unwrap it to reference it.在这种情况下 foo 仍然是可选的,但您不必打开它来引用它。 That means any time you try to reference foo, you crash if it's nil.这意味着任何时候你尝试引用 foo,如果它为 nil,你就会崩溃。

So this code:所以这段代码:

var foo: String!


let upperFoo = foo.capitalizedString

Will crash on reference to foo's capitalizedString property even though we're not force-unwrapping foo.即使我们没有强制解包 foo,也会在引用 foo 的 capitalizedString 属性时崩溃。 the print looks fine, but it's not.打印看起来不错,但事实并非如此。

Thus you want to be really careful with implicitly unwrapped optionals.因此,您要非常小心隐式展开的选项。 (and perhaps even avoid them completely until you have a solid understanding of optionals.) (在您对可选选项有充分的了解之前,甚至可能完全避免使用它们。)

Bottom line: When you are first learning Swift, pretend the "!"底线:当你第一次学习 Swift 时,假装“!” character is not part of the language.字符不是语言的一部分。 It's likely to get you into trouble.这很可能会给你带来麻烦。

Since the above answers clearly explains how to play safely with Optionals.由于上述答案清楚地解释了如何安全地使用 Optionals。 I will try explain what Optionals are really in swift.我将尝试快速解释 Optionals 的真正含义。

Another way to declare an optional variable is另一种声明可选变量的方法是

var i : Optional<Int>

And Optional type is nothing but an enumeration with two cases, ie而可选类型只不过是一个有两种情况的枚举,即

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

So to assign a nil to our variable 'i'.因此,将 nil 分配给我们的变量“i”。 We can do var i = Optional<Int>.none or to assign a value, we will pass some value var i = Optional<Int>.some(28)我们可以做var i = Optional<Int>.none或分配一个值,我们将传递一些值var i = Optional<Int>.some(28)

According to swift, 'nil' is the absence of value.根据 swift 的说法,“nil”是没有价值的。 And to create an instance initialized with nil We have to conform to a protocol called ExpressibleByNilLiteral and great if you guessed it, only Optionals conform to ExpressibleByNilLiteral and conforming to other types is discouraged.并创建一个用nil初始化的实例 我们必须遵守一个名为ExpressibleByNilLiteral的协议,如果您猜到了,那就太好了,只有Optionals符合ExpressibleByNilLiteral并且不鼓励符合其他类型。

ExpressibleByNilLiteral has a single method called init(nilLiteral:) which initializes an instace with nil. ExpressibleByNilLiteral有一个名为init(nilLiteral:)的方法,它用 nil 初始化一个实例。 You usually wont call this method and according to swift documentation it is discouraged to call this initializer directly as the compiler calls it whenever you initialize an Optional type with nil literal.您通常不会调用此方法,并且根据 swift 文档,不鼓励直接调用此初始化程序,因为每当您使用nil文字初始化 Optional 类型时编译器都会调用它。

Even myself has to wrap (no pun intended) my head around Optionals :D Happy Swfting All .甚至我自己也不得不(不是双关语)把我的脑袋绕在 Optionals 上:D Happy Swfting All

First, you should know what an Optional value is.首先,您应该知道什么是 Optional 值。 You can step to The Swift Programming Language for detail.您可以参考 The Swift Programming Language了解详细信息。

Second, you should know the optional value has two statuses.其次,您应该知道可选值有两种状态。 One is the full value, and the other is a nil value.一个是完整值,另一个是零值。 So before you implement an optional value, you should check which state it is.所以在你实现一个可选值之前,你应该检查它是哪个状态。

You can use if let ... or guard let ... else and so on.您可以使用if let ...guard let ... else等等。

One other way, if you don't want to check the variable state before your implementation, you can also use var buildingName = buildingName ?? "buildingName"另一种方式,如果您不想在实现之前检查变量状态,您也可以使用var buildingName = buildingName ?? "buildingName" var buildingName = buildingName ?? "buildingName" instead. var buildingName = buildingName ?? "buildingName"代替。

I had this error once when I was trying to set my Outlets values from the prepare for segue method as follows:当我尝试从 prepare for segue 方法中设置我的 Outlets 值时,我遇到了这个错误,如下所示:

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
        }
    }
}

Then I found out that I can't set the values of the destination controller outlets because the controller hasn't been loaded or initialized yet.然后我发现我无法设置目标控制器插座的值,因为控制器尚未加载或初始化。

So I solved it this way:所以我这样解决了:

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)
        }
    }
}

Destination Controller:目标控制器:

// 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
}

I hope this answer helps anyone out there with the same issue as I found the marked answer is great resource to the understanding of optionals and how they work but hasn't addressed the issue itself directly.我希望这个答案可以帮助任何有相同问题的人,因为我发现标记的答案是了解可选选项及其工作方式的重要资源,但并未直接解决问题本身。

Basically you tried to use a nil value in places where Swift allows only non-nil ones, by telling the compiler to trust you that there will never be nil value there, thus allowing your app to compile.基本上,你试图在 Swift 只允许非 nil 值的地方使用 nil 值,方法是告诉编译器相信你那里永远不会有 nil 值,从而允许你的应用程序编译。

There are several scenarios that lead to this kind of fatal error:有几种情况会导致这种致命错误:

  1. forced unwraps:强制解包:

     let user = someVariable!

    If someVariable is nil, then you'll get a crash.如果someVariable为 nil,那么您将崩溃。 By doing a force unwrap you moved the nil check responsibility from the compiler to you, basically by doing a forced unwrap you're guaranteeing to the compiler that you'll never have nil values there.通过执行强制解包,您将 nil 检查责任从编译器转移到您身上,基本上通过执行强制解包,您向编译器保证您永远不会有 nil 值。 And guess what it happens if somehow a nil value ends in in someVariable ?猜猜如果一个 nil 值以某种方式以someVariable结尾会发生什么?

    Solution?解决方案? Use optional binding (aka if-let), do the variable processing there:使用可选绑定(又名 if-let),在那里进行变量处理:

     if user = someVariable { // do your stuff }
  2. forced (down)casts:强制(向下)强制转换:

     let myRectangle = someShape as! Rectangle

    Here by force casting you tell the compiler to no longer worry, as you'll always have a Rectangle instance there.这里通过强制转换告诉编译器不再担心,因为那里总是有一个Rectangle实例。 And as long as that holds, you don't have to worry.只要这一点成立,您就不必担心。 The problems start when you or your colleagues from the project start circulating non-rectangle values.当您或您的项目同事开始循环非矩形值时,问题就开始了。

    Solution?解决方案? Use optional binding (aka if-let), do the variable processing there:使用可选绑定(又名 if-let),在那里进行变量处理:

     if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle }
  3. Implicitly unwrapped optionals.隐式展开的选项。 Let's assume you have the following class definition:假设您有以下类定义:

     class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } }

    Now, if no-one messes up with the name property by setting it to nil , then it works as expected, however if User is initialized from a JSON that lacks the name key, then you get the fatal error when trying to use the property.现在,如果没有人通过将name属性设置为nil来弄乱它,那么它会按预期工作,但是如果User是从缺少name键的 JSON 初始化的,那么在尝试使用该属性时会出现致命错误.

    Solution?解决方案? Don't use them :) Unless you're 102% sure that the property will always have a non-nil value by the time it needs to be used.不要使用它们 :) 除非您 102% 确定该属性在需要使用时始终具有非零值。 In most cases converting to an optional or non-optional will work.在大多数情况下,转换为可选或非可选将起作用。 Making it non-optional will also result in the compiler helping you by telling the code paths you missed giving a value to that property将其设为非可选还将导致编译器通过告诉您错过的代码路径为该属性赋值来帮助您

  4. Unconnected, or not yet connected, outlets.未连接或尚未连接的插座。 This is a particular case of scenario #3.这是场景#3 的一个特例。 Basically you have some XIB-loaded class that you want to use.基本上你有一些你想要使用的加载 XIB 的类。

     class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! }

    Now if you missed connecting the outlet from the XIB editor, then the app will crash as soon as you'll want to use the outlet.现在,如果您错过了从 XIB 编辑器连接插座,那么一旦您想使用插座,应用程序就会崩溃。 Solution?解决方案? Make sure all outlets are connected.确保所有插座均已连接。 Or use the ?或者使用? operator on them: emailTextField?.text = "my@email.com" .操作员: emailTextField?.text = "my@email.com" Or declare the outlet as optional, though in this case the compiler will force you to unwrap it all over the code.或者将出口声明为可选,尽管在这种情况下,编译器会强制你在整个代码中解开它。

  5. Values coming from Objective-C, and that don't have nullability annotations.来自 Objective-C 的值,并且没有可空性注释。 Let's assume we have the following Objective-C class:假设我们有以下 Objective-C 类:

     @interface MyUser: NSObject @property NSString *name; @end

    Now if no nullability annotations are specified (either explicitly or via NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END ), then the name property will be imported in Swift as String!现在,如果没有指定可空性注释(显式地或通过NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END ),那么name属性将在 Swift 中作为String! (an IUO - implicitly unwrapped optional). (一个 IUO - 隐式展开的可选)。 As soon as some swift code will want to use the value, it will crash if name is nil.一旦一些 swift 代码想要使用该值,如果name为 nil,它将崩溃。

    Solution?解决方案? Add nullability annotations to your Objective-C code.向您的 Objective-C 代码添加可空性注释。 Beware though, the Objective-C compiler is a little bit permissive when it comes to nullability, you might end up with nil values, even if you explicitly marked them as nonnull .但请注意,Objective-C 编译器在可空性方面有点宽容,即使您明确地将它们标记为nonnull ,您最终也可能会得到 nil 值。

This is more of a important comment and that why implicitly unwrapped optionals can be deceptive when it comes to debugging nil values.这是一个更重要的评论,以及为什么在调试nil值时隐式展开的选项可能具有欺骗性。

Think of the following code: It compiles with no errors/warnings:想想下面的代码:它编译时没有错误/警告:

c1.address.city = c3.address.city

Yet at runtime it gives the following error: Fatal error: Unexpectedly found nil while unwrapping an Optional value然而在运行时它给出了以下错误:致命错误:在展开可选值时意外发现 nil

Can you tell me which object is nil ?你能告诉我哪个对象是nil吗?

You can't!你不能!

The full code would be:完整的代码是:

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
}

Long story short by using var address : Address!长话短说,使用var address : Address! you're hiding the possibility that a variable can be nil from other readers.隐藏了变量可能对其他读者nil的可能性。 And when it crashes you're like "what the hell?! my address isn't an optional, so why am I crashing?!.当它崩溃时,你会说“这到底是什么?!我的address不是可选的,那我为什么会崩溃?!。

Hence it's better to write as such:因此最好这样写:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

Can you now tell me which object it is that was nil ?你现在能告诉我是哪个对象是nil吗?

This time the code has been made more clear to you.这次代码对您来说更清楚了。 You can rationalize and think that likely it's the address parameter that was forcefully unwrapped.您可以合理化并认为可能是address参数被强制解包。

The full code would be :完整的代码是:

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
}

The errors EXC_BAD_INSTRUCTION and fatal error: unexpectedly found nil while implicitly unwrapping an Optional value appears the most when you have declared an @IBOutlet , but not connected to the storyboard .错误EXC_BAD_INSTRUCTIONfatal error: unexpectedly found nil while implicitly unwrapping an Optional value当您声明了@IBOutlet时出现最多,但未连接到storyboard

You should also learn about how Optionals work, mentioned in other answers, but this is the only time that mostly appears to me.您还应该了解其他答案中提到的Optionals的工作原理,但这是唯一一次出现在我看来。

If you get this error in CollectionView try to create CustomCell file and Custom xib also.如果您在 CollectionView 中遇到此错误,请尝试创建 CustomCell 文件和自定义 xib。

add this code in ViewDidLoad() at mainVC.在 mainVC 的 ViewDidLoad() 中添加此代码。

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

Xcode 12 iOS 14 Swift 5 Xcode 12 iOS 14 斯威夫特 5

My problem was the type of navigation as I called the vie controller direct without instantiating the storyboard so that's mean data was not set yet from the storyboard.我的问题是导航类型,因为我直接调用 vie 控制器而不实例化情节提要,所以这意味着尚未从情节提要中设置数据。

When you navigate, navigate with导航时,使用

let homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "home") as? HomeEventsViewController
    homeViewController?.modalTransitionStyle = .crossDissolve
    homeViewController?.modalPresentationStyle = .fullScreen
    view.present(homeViewController ?? UIViewController(), animated: true, completion: nil)

Hopefully it works :-)希望它有效:-)

Swift 5.7 + Swift 5.7 +

if let shorthand for shadowing an existing optional variable if let隐藏现有可选变量的简写

Above answers clearly explains why this issue arises and how to handle this issue.上面的答案清楚地解释了为什么会出现这个问题以及如何处理这个问题。 But there is a new way of handling this issue from swift 5.7+ onwards.但是从swift 5.7+开始有一种处理这个问题的新方法

var myVariable: Int?

Previously之前

if let myVariable = myVariable {
    //this part get executed if the variable is not nil
}else{
    //this part get executed if the variable is nil
}

now现在

here now we can omit the right hand side of the expression.现在我们可以省略表达式的右侧。

if let myVariable {
    //this part get executed if the variable is not nil
}else{
    //this part get executed if the variable is nil
}

Previously we had to repeat the referenced identifier twice, which can cause these optional binding conditions to be verbose, especially when using lengthy variable names.以前我们必须重复引用的标识符两次,这会导致这些可选的绑定条件变得冗长,尤其是在使用冗长的变量名时。

But now there is a shorthand syntax for optional binding by omitting the right hand side of the expression.但是现在有一个可选绑定的简写语法,可以省略表达式的右侧。

Same thing is applicable for guard let statement.同样的事情也适用于guard let语句。

For more details :更多详情

Proposal for if-let shorthand if-let 速记的提案

I came across this error while making a segue from a table view controller to a view controller because I had forgotten to specify the custom class name for the view controller in the main storyboard.我在从表视图控制器到视图控制器进行转场时遇到了这个错误,因为我忘记在主情节提要中为视图控制器指定自定义类名。

Something simple that is worth checking if all else looks ok一些简单的东西,如果其他一切看起来都很好,值得检查

If my case I set a variable to UILabel which was nil.如果我的情况是,我将一个变量设置为 UILabel,它是 nil。

So I fixed it and thereafter it did not throw the error.所以我修复了它,此后它没有抛出错误。

Code snippet代码片段

class ResultViewController: UIViewController {

    @IBOutlet weak var resultLabel: UILabel!
    var bmiValue=""
    override func viewDidLoad() {
        super.viewDidLoad()
        print(bmiValue)
        resultLabel.text=bmiValue //where bmiValue was nil , I fixed it and problem was solved

    }
    
    @IBAction func recaculateBmi(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
    

}

in simple words you are trying to use a value of the optional variable which is nil.简而言之,您正在尝试使用 nil 的可选变量的值。 quick fix could be use guard or if let instead of force unwrap like putting !快速修复可以是使用guardif let而不是像 put 一样强制展开! at the end of variable在变量的末尾

这个问题可能是重复的,但这是因为它是一个常见问题,但这里有一篇完整的博客文章,供您全面理解

花一些时间,可能对其他人有用:插座对我来说是零(因此抛出错误:在展开一个Optional值时意外发现nil)是因为: IBOutlet是nil,但是它连接到情节提要中,Swift

This is because you are trying to use a value which can possible be nil, but you decided you don't want to have to check it, but instead assume its set when you uses it and define it as !, there are different philosophies on use of variable set as force unwrap, some people are against there use at all, I personal think they are ok for things that will crash all the time and are simple to reason about, usually references to resource, like outlets to xib files, or uses of images with you app that are part of your assets, if these are not set up properly, you app is going to crash straight away, for a very obvious reason, you can get into difficult when the order of objects being created can be uncertain, and trying to reason solutions to this can be difficult, it usually means a bad design as even it you make them optional, calls to you optional variable may not ever be executed, some projects can demand use of force unwraps for security reasons, things like banking apps, because they wa这是因为您正在尝试使用一个可能为 nil 的值,但您决定不必检查它,而是在使用它时假设它的集合并将其定义为!,有不同的哲学使用变量集作为强制解包,有些人根本反对使用,我个人认为它们适用于那些会一直崩溃并且易于推理的东西,通常是对资源的引用,例如 xib 文件的出口,或者在您的应用程序中使用属于您资产一部分的图像,如果这些设置不正确,您的应用程序将立即崩溃,由于一个非常明显的原因,当创建对象的顺序可能会变得困难时不确定,并且试图推理解决方案可能很困难,这通常意味着一个糟糕的设计,因为即使你将它们设为可选,对你可选变量的调用可能永远不会执行,出于安​​全原因,某些项目可能要求使用强制解包,诸如银行应用程序之类的东西,因为它们是nt the app to crash rather then continue to work in an unplanned way. nt 应用程序崩溃,而不是继续以计划外的方式工作。

Swift 5 and above ( optionals and optional chaining ) Swift 5及更高版本( 可选和可选链接

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Swift 3:致命错误:在展开可选值时意外发现 nil - Swift 3: fatal error: unexpectedly found nil while unwrapping an Optional value UIImagePickerController致命错误:解开可选值时意外发现nil - UIImagePickerController fatal error: unexpectedly found nil while unwrapping an Optional value 致命错误:在didDeselectRowAt中解开Optional值时意外发现nil - fatal error: unexpectedly found nil while unwrapping an Optional value in didDeselectRowAt 致命错误:解开可选值(lldb)时意外发现nil - fatal error: unexpectedly found nil while unwrapping an Optional value (lldb) @IBInspectable致命错误:在展开Optional值时意外发现nil - @IBInspectable fatal error: unexpectedly found nil while unwrapping an Optional value 致命错误:解开可选值时意外发现nil-委托 - fatal error: unexpectedly found nil while unwrapping an Optional value - delegate 致命错误:在展开可选值Swift时意外发现nil - fatal error: unexpectedly found nil while unwrapping an Optional value, Swift 致命错误:在展开可选值 (lldb) 时意外发现 nil - fatal error: unexpectedly found nil while unwrapping an Optional value (lldb) 致命错误:在展开可选值 (lldb) 时意外发现 nil - fatal error: unexpectedly found nil while unwrapping an Optional value (lldb) Alamofire,致命错误:在解开 Optional 值时意外发现 nil - Alamofire, fatal error: unexpectedly found nil while unwrapping an Optional value
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM