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

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

すみません、大遅刻しました。[Swift愛好会 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/love_swift)22日目の投稿です。


# 前置き: 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](https://love-swift.connpass.com/event/41463/)でも飛び入りで発表させていただきました。話の流れ次第で、飛び入りでそういうこともできちゃうのが、この勉強会の魅力ですね！
とAdvent Calendar主催にちなんだダイマをしつつ。
この記事では、 `as AnyObject` の時に何が起こるのかを、具体的なコードとともに確認してみます。

# `as AnyObject` は値型をboxingする

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

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

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

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

```swift
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** <sub>(String, Array, Dictionary, Set, etcのこと)</sub>の場合、
    - 既存の `_ObjectiveCBridgeable` プロトコルを流用
    - SE-0116ではノータッチ
        - （けど、将来的にはもっと多くの型をbridgeableにしたい）
- **その他の値型**の場合、immutableなclassでboxingする
    - 元の値型に戻せるなら、内部実装を外に見せる必要はない

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

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

```swift
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

```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` した場合に、適切な型が選択されます。


```swift
do {
    let anyNum = 1 as AnyObject

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

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

```swift
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



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

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

#### さらに脱線。

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

```swift
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.Bool` を `as AnyObject` したときには `_SwiftTypePreservingNSNumber` ではなくてこっちが使われたりする…みたいな話もあるんだけど、そこらへんの深掘りは、まあまた別の機会がありましたら。

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

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

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

```swift
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

```objc

// _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](https://developer.apple.com/library/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html)を見ると、 AnyObject周りの実装はアップデートのたびにちょくちょく調整されているようです。
今後の動向にも注目です。