web-dev-qa-db-ja.com

エラーを無条件にNSErrorに変換できるのはなぜですか?

多くの場合、フレームワークからSwift Errorオブジェクトを受け取ります。これは、実際にはNSErrorです。

その情報(例:code)にアクセスするには、それをNSErrorにキャストする必要があります。

(error as NSError).code == ....

なぜこれは無条件のasなのですか? Errorに準拠する独自のエラークラスを設計した場合、それは必ずしもNSErrorであるとは限りません。では、これがこのキャストを実行する正しい方法であるとしたらどうでしょうか。

型システムに何らかの特殊なケースはありますか?これは、アップキャストのように動作するダウンキャストです。

8
Bill

ErrorNSErrorに変換できる機能はコンパイラにハードコードされており、実際のブリッジングはSwiftランタイムに実装されていると思います。

runtime/ErrorObject.mm 、私はこのコメントを見つけました:

// This implements the object representation of the standard Error
// type, which represents recoverable errors in the language. This
// implementation is designed to interoperate efficiently with Cocoa libraries
// by:
// - ...
// - allowing a native Swift error to lazily "become" an NSError when
//   passed into Cocoa, allowing for cheap Swift to Cocoa interop

そして この関数

/// Take an Error box and turn it into a valid NSError instance.
id
Swift::_Swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject) {
    ...

  // Otherwise, calculate the domain, code, and user info, and
  // initialize the NSError.
  auto value = SwiftError::getIndirectValue(&errorObject);
  auto type = errorObject->getType();
  auto witness = errorObject->getErrorConformance();

  NSString *domain = getErrorDomainNSString(value, type, witness);
  NSInteger code = getErrorCode(value, type, witness);
  NSDictionary *userInfo = getErrorUserInfoNSDictionary(value, type, witness);

  ...
}

ErrorHandling.rst document は、理論的根拠について次のように述べています。

修飾型名をドメインキーとして使用し、列挙子を次のように使用することで、Errorに準拠する任意のSwift列挙型をNSErrorに変換できるはずです。エラーコード、およびペイロードをユーザーデータに変換します。

(ドキュメントの一部が古くなっている可能性があります。)

そして これは (私は思う)タイプチェッカーの少なくとも1つの部分は、ErrorNSErrorに変換可能であるという情報でした(おそらくもっとあります):

  // Check whether the type is an existential that contains
  // Error. If so, it's bridged to NSError.
  if (type->isExistentialWithError()) {
    if (auto nsErrorDecl = getNSErrorDecl()) {
      // The corresponding value type is Error.
      if (bridgedValueType)
        *bridgedValueType = getErrorDecl()->getDeclaredInterfaceType();

      return nsErrorDecl->getDeclaredInterfaceType();
    }
  }
9
Ole Begemann

これは素晴らしい質問です。

「エラータイプはNSErrorにブリッジできる」とどこかで見たと思いましたが、それはXcodeまたはオンラインのチュートリアルであったに違いありません。

幸いなことに、これは Swift/NSError.Swift から見つかりました。

// NSError and CFError conform to the standard Error protocol. Compiler
// magic allows this to be done as a "toll-free" conversion when an NSError
// or CFError is used as an Error existential.
extension NSError : Error {
  @nonobjc
  public var _domain: String { return domain }

  @nonobjc
  public var _code: Int { return code }

  @nonobjc
  public var _userInfo: AnyObject? { return userInfo as NSDictionary }

  /// The "embedded" NSError is itself.
  @nonobjc
  public func _getEmbeddedNSError() -> AnyObject? {
    return self
  }
}

extension CFError : Error {
  public var _domain: String {
    return CFErrorGetDomain(self) as String
  }

  public var _code: Int {
    return CFErrorGetCode(self)
  }

  public var _userInfo: AnyObject? {
    return CFErrorCopyUserInfo(self) as AnyObject
  }

  /// The "embedded" NSError is itself.
  public func _getEmbeddedNSError() -> AnyObject? {
    return self
  }
}
4
funct7