web-dev-qa-db-ja.com

Swift 3暗黙的にラップされていないオプションのある誤った文字列補間

Swift 3?で文字列補間を使用すると、暗黙的にアンラップされたオプションアンラップされないのはなぜですか?

:プレイグラウンドで次のコードを実行する

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str)")

この出力を生成します:

The following should not be printed as an optional: Optional("Hello")

もちろん、+演算子で文字列を連結できますが、アプリのどこでも文字列補間を使用していますが、これが原因で動作しなくなりました(バグ?)。

これはバグですか、それともSwift 3?

71
Keiwan

SE-0054 により、ImplicitlyUnwrappedOptional<T>は特殊タイプではなくなりました。現在はOptional<T>のみです。

宣言には暗黙的にラップされていないオプションT!として注釈を付けることができますが、そうすることで、隠された属性を追加して、アンラップされたタイプTを要求するコンテキストで値が強制的にアンラップされる可能性があることをコンパイラに通知します。実際のタイプはT?になりました。

したがって、この宣言を考えることができます。

var str: String!

実際には次のようになります:

@_implicitlyUnwrapped // this attribute name is fictitious 
var str: String?

コンパイラだけがこの@_implicitlyUnwrapped属性を認識しますが、許可されるのは、str(そのラップされていないタイプ)を要求するコンテキストでのStringの値の暗黙的なラップ解除です。

// `str` cannot be type-checked as a strong optional, so the compiler will
// implicitly force unwrap it (causing a crash in this case)
let x: String = str

// We're accessing a member on the unwrapped type of `str`, so it'll also be
// implicitly force unwrapped here
print(str.count)

ただし、strを強力なオプションとして型チェックできる他のすべてのケースでは、次のようになります。

// `x` is inferred to be a `String?` (because we really are assigning a `String?`)
let x = str 

let y: Any = str // `str` is implicitly coerced from `String?` to `Any`

print(str) // Same as the previous example, as `print` takes an `Any` parameter.

また、コンパイラーは、強制的なアンラップよりも常にそれを扱うことを好みます。

提案が述べているように(強調鉱山):

を強力なオプション型で明示的に型チェックできる場合、それはになります。ただし、タイプチェッカーは、必要に応じてオプションの強制にフォールバックします。この動作の効果は、T!として宣言された値を参照する式の結果がT型またはT?型のいずれかになることです。

文字列補間に関しては、コンパイラは内部で _ExpressibleByStringInterpolation protocol の初期化子を使用して、文字列補間セグメントを評価します。

/// Creates an instance containing the appropriate representation for the
/// given value.
///
/// Do not call this initializer directly. It is used by the compiler for
/// each string interpolation segment when you use string interpolation. For
/// example:
///
///     let s = "\(5) x \(2) = \(5 * 2)"
///     print(s)
///     // Prints "5 x 2 = 10"
///
/// This initializer is called five times when processing the string literal
/// in the example above; once each for the following: the integer `5`, the
/// string `" x "`, the integer `2`, the string `" = "`, and the result of
/// the expression `5 * 2`.
///
/// - Parameter expr: The expression to represent.
init<T>(stringInterpolationSegment expr: T)

したがって、コードによって暗黙的に呼び出された場合:

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str)")

strの実際の型はString?であるため、デフォルトでは、コンパイラが汎用プレースホルダーTを推測します。したがって、strの値は強制的にラップ解除されず、オプションの説明が表示されます。

IUOを文字列補間で使用するときに強制的にラップ解除する場合は、単に強制的にアンラップ演算子!を使用できます。

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str!)")

または、コンパイラーに暗黙的にラップを強制的に強制的に解除させるために、オプションではない型(この場合はString)に強制することができます。

print("The following should not be printed as an optional: \(str as String)")

もちろん、strnilの場合、どちらもクラッシュします。

71
Hamish