🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

4回答

905閲覧

アップキャストについて

aae_11

総合スコア178

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2019/12/02 11:44

キャストについて調べていたのですが、疑問点があり、質問させて貰いました。

let test2: String = "sample" print("type Of test2: (type(of:test2))") let test1: Any = test2 as Any print("type Of test1: (type(of:test1))")

こちらなのですが、printの結果はいずれも「String」型になってしまいます。
アップキャストが行われているのだとしたら、test1はanyになるのではないかと思うのですが、何故String型のままなのでしょうか...?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

公式ドキュメントに書いてあります

公式ドキュメントのここが、わからないは、質問に普通に追記でいいと思いますよ。
むしろ、コメントは検索対象にならないので、せっかくの書いてもらったのに質問が埋もれます。

「このtype(of:)関数を使用して、特に動的タイプが静的タイプと異なる場合に、値の動的タイプを見つけることができます。値の静的な型は、既知のコンパイル時の値の型です。値の動的な型は、実行時の値の実際の型であり、具体的な型のサブタイプになる場合があります。」

まず、値の静的タイプの説明として、「既知のコンパイル時の値の型です。」とありますが、これが何を示しているのかが分からないです。そして、動的なタイプの説明として、「具体的な型のサブタイプ」とありますが、こちらの意味もよく分からないです

 
静的タイプ(既知のコンパイル時の型)について

「既知のコンパイル時の値の型です。」

ソースコードに定義されている型のことです。
動的タイプは実行時の実態の型です。

swift

1func printInfo(_ value: Any) { 2 let t = type(of: value) 3 print("'(value)' of type '(t)'") 4} 5 6let count: Int = 5 7printInfo(count) 8// '5' of type 'Int'

countは、静的タイプはIntです。
動的なタイプは5なのでIntです。

このcount変数printInfo関数へ渡していますが

printInfo関数内での仮引数、value の静的タイプはAnyです。
そして、type(of:)で取得した動的タイプはIntです。

質問者さんのコードでいうと

Swift

1let test2: String = "sample" 2print("type Of test2: (type(of:test2))") 3let test1: Any = test2 as Any 4print("type Of test1: (type(of:test1))")

test2のStringと、test1のAnyの部分が静的タイプです。

「具体的な型のサブタイプ」

端的にいっちゃうと、ダウンキャストしたものです。
関数の引数は汎用的にするためAnyとまではいかないですが、親の型を定義(静的タイプ)とすることが多いと思います。
元の型でなく、実際のインスタンスである、継承した型(サブタイプ)を取得するということです。

大元の質問に戻ると

test1はanyになるのではないかと思うのですが

Anyになると思っていることそのものが勘違いで、本来の型であるStringを取得することが正しいです。

静的?動的?って、混乱してるかもしれませんが、単純な例でいうと。
ソースでStringって定義したから実態がStringになるわけじゃないんです。
以下は、Stringっていう文字がでてこないですが、"sample"の実態はStringなので、type(of:)の結果はStringです。

Swift

1let test1: Any = "sample" 2print("type Of test1: (type(of:test1))")

投稿2019/12/02 12:39

編集2019/12/03 04:09
momon-ga

総合スコア4826

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aae_11

2019/12/02 12:49

リンクどうもです。 しかしながら、リファレンスはどうも難しくて、結構手こずっている部分ではあるんですよね...
momon-ga

2019/12/02 13:03

このコメントは、ドキュメントを噛み砕いて説明してっていうリクエスト? chromの翻訳で、で見たらそのまま書いてあるのだけど。
aae_11

2019/12/02 23:20 編集

まだ勉強を始めたばかりなので、ドキュメントは難しく感じる部分ではあります
momon-ga

2019/12/03 01:03 編集

具体的に、どの部分を難しいと感じているのか、質問に追記してもらえますか? ※読む気が無いと遠まわしに言っているのでしたら無視していただいてかまいません
aae_11

2019/12/03 02:04 編集

momon-gaさんに対してのみの、返信なので、こちらに記載させて頂きます。 公式ドキュメントの以下の箇所で言いますと、 「このtype(of:)関数を使用して、特に動的タイプが静的タイプと異なる場合に、値の動的タイプを見つけることができます。値の静的な型は、既知のコンパイル時の値の型です。値の動的な型は、実行時の値の実際の型であり、具体的な型のサブタイプになる場合があります。」 まず、値の静的タイプの説明として、「既知のコンパイル時の値の型です。」とありますが、これが何を示しているのかが分からないです。そして、動的なタイプの説明として、「具体的な型のサブタイプ」とありますが、こちらの意味もよく分からないです
aae_11

2019/12/03 04:09 編集

追記ありがとうございます。 ご解説頂きましたおかげで、静的タイプや動的タイプについてなど、大分理解できてきました。 未だ完全に理解できていない部分がある為、確認の為、お聞きしたい部分がございます。 「let count: Int = 5」こちらの部分で、静的タイプは「Int」であり、動的タイプも5の為「Int」であるとの記載があったのですが、このことから、静的タイプとは最初に定義した際の型であり、動的タイプは値を代入するなどし、値が代わりその値の型も変更になった場合の変数であるとの解釈で合っていますでしょうか? 自分の理解で合っているとしますと「本来の型であるStringを取得することが正しいです」こちらのご説明からも「test1」から「String」が出力される理由は、test2本来の型、「let test2: String = "sample"」こちらで指定したStringが「test1」の型として出力されるのではないかと思い、前述させて貰ったような解釈に至りました。
momon-ga

2019/12/03 04:18 編集

動的なタイプっていうのは型の定義(静的)とは別のものです。 MasakiHoriさんの回答は、それをいっていてコンパイラの認識する型と、実行時の実態(インスタンス)は扱いが別ものなので キャストしてもラベルが変わる(コンパイラが認識する型が変わる)だけという表現をしていると思います。 回答の一番最後に追記しました。
guest

0

ベストアンサー

PHPでは、

PHP

1$foo = "5"; // $fooは文字列 2$foo = (int) $foo; // $fooは整数に変わる

というように変数の型が動的に変化する上、実体(インスタンス)も変化するかのように動作します。aae_11さんはおそらくこういう動作を想定しているのではないかと思います。

Swiftではこのような事はできません。Swiftのキャストは、PHPのキャストような型変換機能ではありません。let test1: Any = test2 as Anyという文はtest2Any型に変換してtest1に代入しているのではないのです。

まず、Swiftで型を変えるには、別の型の実体を新たに作り出す必要があります。下記のint1string1を元に、Intの実体を新たに作る事で型変換を行っています。キャストしているのではありません。int2のようにキャストでやろうとすると、エラーになります。FloatDoubleの間でさえ、キャストでは変換できません。

Swift

1let string1 = "5" // string1はString型の"5"になる。 2let int1 = Int(aString)! // int1はInt型の5になる。 3let int2 = string1 as Int // エラー "Cannot convert value of type 'String' to type 'Int' in coercion"

それではSwiftの型キャストとは何でしょう。説明を抜粋します。

Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.

抜粋:: Apple Inc. “The Swift Programming Language (Swift 5.1)”。 Apple Books https://books.apple.com/jp/book/the-swift-programming-language-swift-5-1/id881256329

意訳:型キャストは、実体の型を調べたり、実体のクラス階層のなかでスーパークラスやサブクラスとして扱うための機能です。isas演算子で実装されます。

Swiftのキャストは、スーパークラスやサブクラスの間でしか使えません。違う型に変換しようとするとエラーになります。また、型変換の機能はそもそもありません。

Any型はとても特殊で、どの型でもAnyと相互にキャストできます。
let test1: Any = test2 as Anyという文はtest2Any型とみなして、Any型であるtest1に代入しています。実体はなんら変化しません。その実体を参照している変数の型が違うだけです。

Swiftは暗黙のキャストをしてくれるので、明示的に指定しなければならないのはAnyがからむときくらいですが、たとえばUserDefaultではよく使います。

swift

1let a = UserDefaults.standard.integer(forKey: "storedIntValue") 2let b = UserDefaults.standard.object(forKey: "storedIntValue") as! Int

保存されている"storedIntValue"キーの値がInt型なら、abは同じになります。しかし、Int型じゃない場合、aにはなんらかのIntの値が得られますが、bはキャストに失敗するのでクラッシュします。

なお、危険なのでAnyの使用はライブラリが要求する場合のみに留め、自分で使うのはできるだけ避けるのが定石です。型が提供する利点を破壊する存在なので。

投稿2019/12/03 08:22

eytyet

総合スコア803

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aae_11

2019/12/03 11:20 編集

ご丁寧にご説明くださり、ありがとうございます。 まさしくおっしゃります通り、PHPの挙動と混乱してしまっている部分がございました。 色々な方からご回答を頂き、自分が誤解してしまっていた部分が分かってきたような気がいたします。 そもそも「let int1 = Int(aString)!」こちらは、いわゆる「キャスト」と言われるものであり、「let int2 = string1 as Int」こちらは型キャストと言われるものであるかと思いますが、そもそも「型キャスト」という概念を普通のキャストと同じものだといった勘違いをしてしまっておりました... つまるところ、実体(値そのもの)をキャストするのと、型をキャストするというのでは違ってくるということでしょうか...? しかし、どうも分からない点としましては、実体(値そのもの)の型と変数の型を分けて使用するメリットってあるのかなって思ってしまうんですよね... わざわざ分けて考えたら、余計まどろっこしくなってしまい自分は混乱してしまう気がいたします... そもそも実体の型と変数の型を一緒にした方が、変数にどういうものが入っているのだとかそういったことが分かりやすいと思いますし、分ける意味がいまいち理解しきれない部分ではあります... 勿論何かしらメリットは存在するのだとは思うのですが...
eytyet

2019/12/03 11:40

私は「キャスト」と「型キャスト」を区別せずに用いてしまっていました。ご説明をありがとうございます。Swiftには「型キャスト」はあっても、型変換という意味での「キャスト」はないという事と思います。あるのはコンストラクタを使った型変換ですが、それらはそれぞれの型のもつ機構であって、言語仕様とは別の仕組みのように思います。 型キャストのメリットは、拡張性だと理解しています。我々がUIViewを拡張して作ったCustomViewをUIViewとして扱えるから、既存のUIViewControllerがCustomViewをUIViewとみなして扱えるのです。そうでなかったら、CustomViewを使うためにUIViewControllerを拡張したUICustomViewControllerが必要になってしまいます。
aae_11

2019/12/03 12:06

ご返信ありがとうございます。 「拡張性」なのですね。正直に申しまして、完全には理解しきれてはいないのですが、誤解してしまっていた部分にも気づけ、かなり理解できて参りました。Swiftの学習を続けていけば完璧に理解できてくるかもしれませんし、現段階では、頂いたご回答を忘れずに、学習を続けて参りたいと思います。
guest

0

まず、変数(または定数)の型と、その変数に入っている値の型は一致しているとは限りません。
例えばlet a: Any = "text"であれば aの型はAnyですが、実際に入っている値の型はstringです。

で、アップキャストはキャスト対象をスーパークラスとして使えるようにするだけで、値自体の何かしらを書き換えるわけではありません。
なので、
let test1: Any = test2 as Any

let test1: Any = test2
と書いてるのと一緒です。

次にtype()についてですが、
変数自体の型はソースコードを見ればわかる(=静的)ので調べる必要がありません、例えばstring型の変数s変数の型はstringです。
変数に入っている値の型はソースコードを見ても確定するとは限りません。実行時に決まります。(=動的)
type()関数は動的なほうの型を調べる関数です。


おまけ:キャストについてのリファレンス

type-casting-operator

The as operator performs a cast when it is known at compile time that the cast always succeeds, such as upcasting or bridging. Upcasting lets you use an expression as an instance of its type’s supertype, without using an intermediate variable. The following approaches are equivalent:

func f(_ any: Any) { print("Function for Any") } func f(_ int: Int) { print("Function for Int") } let x = 10 f(x) // Prints "Function for Int" let y: Any = x f(y) // Prints "Function for Any" f(x as Any) // Prints "Function for Any"
  • as演算子はコンパイル時には成功するとわかっているキャスト(例えばアップキャスト)に使う
  • (アップキャストを使うと)中間変数を使わずに、式(expression)をスーパータイプとして使える

投稿2019/12/03 05:52

編集2019/12/03 05:53
ozwk

総合スコア13551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aae_11

2019/12/03 07:00

ご回答ありがとうございます。 色々な方からのご回答をいただいているのですが、未だ理解仕切れない部分があります... >let a: Any = "text"であれば aの型はAnyですが、実際に入っている値の型はstringです。 こちらについてなのですが、実際に入っている値の型がStringであれば、aにAnyなどと他の型を指定するのは、どうもおかしいような気がしてしまいます... aにAnyとしてしまっては、String型として扱えない=String型のメソッドが使用できない訳ですし、なんのメリットがあるのかいまいち分からないです...
guest

0

コンパイラが認識する型が変わるだけといえば分かりますか?


追記
(注意:以下の説明で用いているラベルという単語は説明のために使っているものでSwiftで一般的に用いられる用語ではありません)

コンパイラはコンパイル時に各変数/定数などに型というラベルを張り付けます。
コンパイラはそのラベルを頼りに使用可能なメソッドやプロパティを決定します。

swift

1let a = 0 2let b = a as Any

としたとき実体としてのbはIntのままですが、コンパイラが認識するラベルとしての型はAnyになります。
ですのでbに対してIntのメソッドやプロパティを使用しようとするとコンパイルエラーとなります。

しかし、type(of:)はラベルではなく実体を見ていますので実体のMetatypeを返します。

投稿2019/12/02 14:15

編集2019/12/03 02:05
MasakiHori

総合スコア3391

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aae_11

2019/12/02 23:22

ご回答ありがとうございます。 typeOf演算子では実際の判断が難しいということでしょうか...?
MasakiHori

2019/12/03 01:53

コンパイラとtype(of:)は別の情報を参照しているということです
aae_11

2019/12/03 02:07

ご返信ありがとうございます。 そうなりますと、実際のキャスト後の変数の型を確認しようとした場合には、確認可能な方法はありますでしょうか...?
aae_11

2019/12/03 02:20 編集

誤投稿の為、削除しました。
aae_11

2019/12/03 02:27 編集

追記読ませて頂きました。 「コンパイラが認識するラベル」こちらにより、使用できるメソッド、プロパティが変わってくるといった訳なのですね。 存在するのかも分からないのですが、もしこちらの「ラベル(型)」を調べる関数や演算子などありましたら、教えて頂けませんでしょうか...?
aae_11

2019/12/03 02:33

自分は実態というのは良く分からないのですが、初心者故の疑問かもしれないですが、type(of:)でもラベルを判定してくれれば、厄介な混乱も生まれないのに...と思ってしまいますね... 勿論、実態というのを判別するのは重要なことなのかもしれないですが...
MasakiHori

2019/12/03 02:33

SILという中間言語を出力してやればそこに書いています SILに関しては回答の範囲を超えすぎる話題ですので詳細はご自身で調べてください
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問