post Image
`as AnyObject` で何が起こるのか

こんばんは、クリスマスまであと3日ですね。
…。

すみません、大遅刻しました。Swift愛好会 Advent Calendar 201622日目の投稿です。

前置き: SE-0116(id-as-any)とは

Swift2→Swift3に移行して、特にFoundationを利用する際に大きな変更を感じるのが、引数の型が AnyObject から Any に変わった点ではないでしょうか。

  • この変更が何故必要だったのか
  • どういう仕様なのか

については、以下のSwift Evolutionに書かれています。

swift-evolution/0116-id-as-any.md at master · apple/swift-evolution
https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md

以前、自分はここらへんを読み解いて日本語スライドにしました。

5分でわかるSE-0116(id-as-any) // Speaker Deck
https://speakerdeck.com/takasek/5fen-dewakaruse-0116-id-as-any

なお、このスライドはSwift愛好会 vol12でも飛び入りで発表させていただきました。話の流れ次第で、飛び入りでそういうこともできちゃうのが、この勉強会の魅力ですね!
とAdvent Calendar主催にちなんだダイマをしつつ。
この記事では、 as AnyObject の時に何が起こるのかを、具体的なコードとともに確認してみます。

as AnyObject は値型をboxingする

まず基本のbridgeの機能。
値型変数は、 as AnyObject の時点でclassインスタンスに変換されます。これをboxingといいます。

do {
    let value = 1
    let a = value as AnyObject
    let b = a

    a === b //true
    //as AnyObjectすると、classインスタンスにboxingされるので、 === による同一性比較が可能
}

なお、 as AnyObject のタイミングが異なれば、boxingされるclassインスタンスは別物になります。

do {
    let value = 1
    let a = value as AnyObject
    let b = value as AnyObject

    a === b //false
}

もうちょい細かい as AnyObject の詳細設計

as AnyObject は、Swift Evolutionによれば、

  • クラス型 の場合、何もしない。
  • Bridged value types (String, Array, Dictionary, Set, etcのこと)の場合、

    • 既存の _ObjectiveCBridgeable プロトコルを流用
    • SE-0116ではノータッチ
      • (けど、将来的にはもっと多くの型をbridgeableにしたい)
  • その他の値型の場合、immutableなclassでboxingする

    • 元の値型に戻せるなら、内部実装を外に見せる必要はない

という挙動になっているそうです。確認してみましょう。

クラス型 の場合、何もしない。

do {
    class C {}
    let c = C() // (C #1)
    let bridgedC = c as AnyObject // (C #1)

    c === bridgedC //true
}

元の c と、それを as AnyObject にした bridgedC が同一インスタンスなのが分かりますね。

Bridged value typesのブリッジは、既存の _ObjectiveCBridgeable プロトコルを流用

_ObjectiveCBridgeable プロトコルについては、@es_kumagaiさんによる以下のQiita記事が詳しいです。

Objective-C Bridge の仕組とそこから感じたこと – Qiita
http://qiita.com/es_kumagai/items/bc154ee0a8a842e3b3ca

記事中に紹介されているのはSwift2時代のインタフェースで、最新のインタフェースはこんな感じですね。
https://github.com/apple/swift/blob/master/stdlib/public/core/BridgeObjectiveC.swift

public protocol _ObjectiveCBridgeable {
  associatedtype _ObjectiveCType : AnyObject

  func _bridgeToObjectiveC() -> _ObjectiveCType

  static func _forceBridgeFromObjectiveC(
    _ source: _ObjectiveCType,
    result: inout Self?
  )

  @discardableResult
  static func _conditionallyBridgeFromObjectiveC(
    _ source: _ObjectiveCType,
    result: inout Self?
  ) -> Bool

  static func _unconditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType?)
      -> Self
}

これらのプロトコルに適合している場合、 as AnyObject した場合に、適切な型が選択されます。

do {
    let anyNum = 1 as AnyObject

    type(of: anyNum) //_SwiftTypePreservingNSNumber.Type
}

注意点として、一度値型へとキャストされた場合は、boxingは解除されてしまいます。

do {
    let value = 1 as AnyObject
    let a = value
    let b = value as Any as AnyObject // AnyならNSNumberのままなんだけど、

    a === b //true
}

do {
    let value = 1 as AnyObject
    let a = value
    let b = value as Int as AnyObject // as Intだとboxingが解除されてInt型になっているので…

    a === b //false
}

ところで、 _SwiftTypePreservingNSNumber って?

NSNumberのサブクラスだそうです。
NSNumberだと実際の数値型を覚えていないので、ちゃんとブリッジできるようオリジナルの型を保持してdynamic castできるようにしたもの …らしい。

proposal:
https://github.com/apple/swift-evolution/blob/master/proposals/0139-bridge-nsnumber-and-nsvalue.md

implementation:
https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/TypePreservingNSNumber.mm

do {
    let anyNum = 1 as AnyObject
    let nsNumFromLiteral = 1 as NSNumber

    type(of: anyNum) //_SwiftTypePreservingNSNumber.Type
    type(of: nsNumFromLiteral) //__NSCFNumber.Type
}

さらに脱線。

ちなみに、NSNumberへの変換時に、それがInt型か、リテラルか、イニシャライザを通しているか、などで型が微妙に違ったりもします。

do {
    let nsNumFromLiteral = 1 as NSNumber
    let nsNumFromIntLiteralInitializer = NSNumber(integerLiteral: 1)

    let intOne: Int = 1
    let nsNumFromInt = intOne as NSNumber

    let anyNum = 1 as AnyObject

    type(of: nsNumFromLiteral) //__NSCFNumber.Type
    type(of: nsNumFromIntLiteralInitializer) //__NSCFNumber.Type

    type(of: nsNumFromInt) //_SwiftTypePreservingNSNumber.Type
    type(of: anyNum) //_SwiftTypePreservingNSNumber.Type
}

__NSCFNumber は数値リテラルからのブリッジ、あるいはNSNumber.initを通した場合に使われる型。
あと、 __NSCFBoolean は真偽値の表現で、 Swift.Boolas AnyObject したときには _SwiftTypePreservingNSNumber ではなくてこっちが使われたりする…みたいな話もあるんだけど、そこらへんの深掘りは、まあまた別の機会がありましたら。

…脱線しすぎました。そろそろ本筋に戻りましょう。

その他の値型の場合、immutableなclassでboxingする

ここまで、クラス型Bridged value types について、 as AnyObject したときに何が起こるかを確認したところでしたね。最後に、 その他の値型について確認します。

do {
    struct S {}
    let s = S() //(S #1)
    let bridgesS = s as AnyObject //(S #1)()

    type(of: s) //(S #1).Type
    type(of: bridgesS) //_SwiftValue.Type
}

_SwiftValue とかいう奴が出てきました。

インタフェースはこんな感じ。
https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftValue.h


// _SwiftValue is an Objective-C class, but we shouldn't interface with it
// directly as such. Keep the type opaque.
#if __OBJC__
@class _SwiftValue;
#else
typedef struct _SwiftValue _SwiftValue;
#endif

namespace swift {

/// Bridge a Swift value to an Objective-C object by boxing it as a _SwiftValue.
_SwiftValue *bridgeAnythingToSwiftValueObject(OpaqueValue *src,
                                              const Metadata *srcType,
                                              bool consume);

/// Get the type metadata for a value in a Swift box.
const Metadata *getSwiftValueTypeMetadata(_SwiftValue *v);

/// Get the value out of a Swift box along with its type metadata. The value
/// inside the box is immutable and must not be modified or taken from the box.
std::pair<const Metadata *, const OpaqueValue *>
getValueFromSwiftValue(_SwiftValue *v);

/// Return the object reference as a _SwiftValue* if it is a _SwiftValue instance,
/// or nil if it is not.
_SwiftValue *getAsSwiftValue(id object);

/// Find conformances for SwiftValue to the given list of protocols.
///
/// Returns true if SwiftValue does conform to all the protocols.
bool findSwiftValueConformances(const ProtocolDescriptorList &protocols,
                                const WitnessTable **tablesBuffer);

} // namespace swift

コメントにめっちゃ書かれている通り、 _SwiftValue は直接取り扱うべきものではないようです。

まあ、Swiftの世界だけで完結できるならObj-Cでの実装の詳細なんて気にする必要ないし、もし意識する必要があるなら _ObjectiveCBridgeable に適合させればいい。
となれば、 _SwiftValue は本当にただのboxingの仕組みさえ提供していればいいってわけですね。

まとめ

以上、 as AnyObject でどのようにboxingが起こるかについて見てきました。
Xcode8 Release Noteを見ると、 AnyObject周りの実装はアップデートのたびにちょくちょく調整されているようです。
今後の動向にも注目です。


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。