Any型のオブジェクトをProtocolにキャストしようとすると失敗する場合の対策と原因

#TL;DR
- Any型(Objective-Cで言うところのid型)にProtocolに準拠した値型オブジェクトを`as AnyObject`を行って格納し、さらにそのAny型のオブジェクトをProtocolにキャストしようとすると失敗する。

- 格納したAny型のオブジェクトを一度`as AnyObject`してから、Protocolへキャストすると成功する。

- これはObjective-CとSwiftの間でのブリッジの挙動によって起こる。

- Swift3の頃からIssueに上げられているが現在（Xcode10.2, Swift5）でも修正されていない。

#何が起こったか
Any型のオブジェクトからプロトコルにキャストしようとしたところ、プロトコルに準拠した値が入っているのにも関わらず、キャストに失敗してしまいました。

以下に簡単に再現できるソースを貼るのでPlayground等で試してみてください。

```swift
import Foundation

protocol Animal {}

struct Dog: Animal {}

let anyAnimal: Any = Dog() as AnyObject

print(anyAnimal as? Animal) // nil
```

`anyAnimal`には、Animalに準拠しているDogの値が入っているにも関わらず、キャストに失敗してnilが出力されてしまいます。

#SR-3871
これはSwift側のバグなのかと思い、調べてみると以下のIssueを発見しました。
[[SR-3871]Protocol passing via objective-c / Any can't be cast back to protocol type](https://bugs.swift.org/browse/SR-3871)

このIssueは、Objective-CのAny型に格納されたProtocolに準拠した値オブジェクトをProtocolにキャストしてAny型から戻そうとすると失敗するという内容に関してのIssueです。

これは現在（Xcode10.2, Swift5)でも解決されていません。

コメントを見てみるとSwiftの値型オブジェクトをObjective-Cで扱うために`as AnyObject`でキャストをした状態でAny型プロパティに格納すると、Protocolへの準拠の判定ができなくなるために、キャストできるかどうかの判定で弾かれるということのようです。

#解決策

一度`as AnyObject`を挟んでからキャストすると、Protocolへのキャストも成功します。
上記のコードであれば以下のような対応を行えば正しくProtocolにキャストされます。

```swift
print((anyAnimal as AnyObject) as! Animal) // __lldb_expr_11.Dog
```

もしくは、Dogをclassに変更することでも、正しくキャストする事ができるようになります。

```swift
import Foundation

protocol Animal {}

class Dog: Animal {} // struct → class

let dog = Dog()
let anyAnimal: Any = dog as AnyObject
print(anyAnimal as! Animal) // __lldb_expr_11.Dog
```

次はどうして、AnyObjectにキャストしたり、Dogをstructからclassに変更するとProtocolへのキャストが成功するのかを探っていきましょう。

#原因
原因にたどり着くためには、まず`as AnyObject`の挙動について理解する必要があります。

##`as AnyObject`の挙動
`as AnyObject`は、Swiftの値型オブジェクトをObjective-Cで扱うために、クラスのインスタンスに変換する機能を持ちます。

クラスのインスタンスに変換するということを詳細に説明すると、

- クラスの型に行った場合は何もしない

- Bridged value typesに行った場合は、`_ObjectiveCBridgeable`プロトコルで指定された型に自動で変換される
	- Bridged value typesであるDictionary、Arrayなどの型は、`_ObjectiveCBridgeable`によってObjective-Cで扱う際の型を指定しているので、NSDictionaryやNSArrayなどの型として扱う事できるといった感じです。
	
- それ以外の場合に行った場合は、Immutableのクラスとして変換する
	- その際 **_SwiftValue** というクラスに変換される

`as AnyObject`に関しては、以前に[@takasek](https://qiita.com/takasek)さんが「[`as AnyObject` で何が起こるのか](https://qiita.com/takasek/items/1977878c966dcaf6cb5b)」という素晴らしい記事を書かれているので、それを読めば、より詳細に理解できるかと思います。

##_SwiftValue in Any
`as AnyObject`の挙動が理解できたところで、最初に挙げたコードを見ていきましょう。

```swift
import Foundation

protocol Animal {}

struct Dog: Animal {}

let anyAnimal: Any = Dog() as AnyObject

print(type(of: anyAnimal)) // _SwiftValue
```

Dog()は値型であり、`_ObjectiveCBridgeable`に準拠しているわけでもないので`as AnyObject`した時点で、`_SwiftValue`に変換されて、Objective-Cの領域であるAnyに格納されます。

もちろん`print(type(of: anyAnimal))`では、`_SwiftValue`が出力されます。

```swift
// Any(_SwiftValue) → Protocol
print(anyAnimal as? Animal) // nil

// Any(_SwiftValue) → Struct
print(anyAnimal as! Dog) // __lldb_expr_11.Dog

// Any(_SwiftValue) → AnyObject(_SwiftValue) → Protocol
print((anyAnimal as AnyObject) as! Animal) // __lldb_expr_11.Dog
```

以上のキャストの結果を見ると、Objective-Cの領域の`Any(id)型`に格納されている`_SwiftValue`に対してProtocolに準拠しているかどうかを判定できないがゆえに、キャストもできないという事のようです。

よって、解決法で挙げたようにSwiftの領域にある`AnyObject型`に一度変換する事で、それを回避する事ができるようになります。

## class
`as AnyObject`はclassに対しては何も行わないというのは先ほど述べた通りです。

```swift
import Foundation

protocol Animal {}

class Dog: Animal {}

let dog = Dog()
let anyAnimal: Any = dog as AnyObject
print(type(of: anyAnimal)) // Dog
print(anyAnimal as! Animal) // __lldb_expr_11.Dog
```

`as AnyObject`をDogに対して行っても、何も行われないため、`_SwiftValue`に変換されず、Any型に格納されていてもProtocolへのキャストに失敗する事がなくなります。

#どういった場合に起こり得るか
今日では、Swiftが浸透しているため、Objective-Cとのブリッジを特に意識する必要はあまりなく、今回述べたような状況に陥ることはほとんどないのではないかと思います。

しかし、実際のところFoundationやOSSのライブラリがObjective-Cで作成されていることで、それを意識していないと、思わぬ落とし穴にはまる事があります。

**Notification** がその最たる例でしょう。

##例：Notification
以下が、サンプルです。

```swift
import Foundation

protocol Animal {}

struct Dog: Animal {}

final class HogeClass {
    
    init() {
        // Notificationを登録する
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.didReceiveNotification(_:)),
                                               name: .init("Hoge"),
                                               object: nil)
    }
    
    // Notificationを受け取る
    @objc
    private func didReceiveNotification(_ sender: Notification) {
    	print(type(of: sender.object!)) // _SwiftValue
        print(sender.object as? Animal) // nil
    }
}

let hoge = HogeClass()
// Notificationを投げる
NotificationCenter.default.post(name: .init("Hoge"), object: Dog())
```

NotificationCenterがpostする際の引数の`object(Any型)`にDogのインスタンスを指定します。

実際に受け取ったHogeClassの`private func didReceiveNotification(_ sender: Notification)`の`sender.object`は、内部で`as AnyObject`が行われているためか、`_SwiftValue`として受け取らざるを得なくなり、Animalにキャストしようとすると失敗し、先ほどの解決法を使用することになります。

#個人的感想
こういったことから、開発中のアプリのソースコードにSwiftとObjective-Cが共存していないとしても、フレームワークやライブラリにObjective-Cが存在している限りは、それらとSwiftとのブリッジの意識は頭の片隅に置いておいたほうが良いでしょう。