質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.50%
Swift

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

Q&A

解決済

1回答

1818閲覧

Swiftの変数について(エクスクラメーションマークの意味)

pegy

総合スコア243

Swift

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

0グッド

0クリップ

投稿2020/03/21 01:32

編集2020/03/21 03:03

(環境)
xcode:11.3
version 5.1.3

(参考文献)
「たった2日でマスターできるIPhoneアプリ開発集中講座 Xcode 11 Swift5 対応」
2020年1月20日 初版第二刷発行
著者: 藤 治仁・小林 加奈子・小林 由憲
発行人: 片柳 秀夫
編集人:三浦 聡
発行所:ソシム株式会社
※下記のコードは165頁のコードリファクタリングの説明箇所のものになります。

上記の書籍でで初心者ながらSwiftを学んでいるのですが、テキストでは以下の//hereの箇所について
fileprivate func soundPlayer(player:inout AVAudioPlayer, path: URL, count: Int) {//here
となっており、おそらく誤植なのかと思うのですが、動作させることができませんでした。

発生しているError
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

そこで以下の修正を行うことで動作をさせることができました。
fileprivate func soundPlayer(player:inout AVAudioPlayer!, path: URL, count: Int) {//here


①ここに気がつくまで1日かかってしまったのですが、正直このエクスクラメーションマークの意味となぜこれがないとerrorが生じるのかまでわかりませんでした。変数でクラスや型を指定する際に
player:inout AVAudioPlayer!の様にエクスクラメーションマークが必要なものや
path: URLの様にエクスクラメーションマークが必要でないものがあるのですが、
このエクスクラメーションマークの意味とどの様に要否を使い分けるのかをご教示願えますでしょうか?


②また、関連してですがテキストによって、変数の設定の仕方について
var cymbalPlayer: AVAudioPlayer!//here3
の様にする場合と
var cymbalPlayer = AVAudioPlayer()
と書いている様なものもあるのですが、どちらでも良いのでしょうか?エクスクラメーションマークとも関連があるかもしれないのでお尋ねさせていただきました。


③また、少し話が逸れてしまい申し訳ないのですが、
soundPlayer(player: &cymbalPlayer ,path: cymbalPath, count: 0)//here2の様に
参照渡しを利用する場合なのですが、これを&を使用せずに値渡しすべきではない理由、またはその弊害がピンと来ないのですが、テキストには
"「soundPlayer」処理後にシンバルの情報を呼び出し元に戻して利用したいので、参照渡しにしている"
と記載されています。実際には&なしでコーディングしても結果、動作させても影響がない様に思えるのですが、具体的にどの様な影響があり、なぜ参照渡しにすべきなのかアドバイスをを頂戴できると大変嬉しいです。

長文大変失礼いたしました。
何卒、よろしくお願い申し上げます。

Swift

1import UIKit 2import AVFoundation 3 4class ViewController: UIViewController { 5 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 // Do any additional setup after loading the view. 9 } 10 11 12 let cymbalPath = Bundle.main.bundleURL.appendingPathComponent("cymbal.mp3") 13 var cymbalPlayer: AVAudioPlayer!//here3 14 15 let guitarPath = Bundle.main.bundleURL.appendingPathComponent("guitar.mp3") 16 var guitarPlayer: AVAudioPlayer! 17 18 let backMusicPath = Bundle.main.bundleURL.appendingPathComponent("backmusic.mp3") 19 var backmusicPlayer: AVAudioPlayer! 20 21 @IBAction func cymbal(_ sender: Any) { 22 soundPlayer(player: &cymbalPlayer ,path: cymbalPath, count: 0)//here2 23 } 24 25 @IBAction func guitar(_ sender: Any) { 26 soundPlayer(player: &guitarPlayer ,path: guitarPath, count: 0) 27 } 28 29 30 @IBAction func play(_ sender: Any) { 31 soundPlayer(player: &backmusicPlayer, path: backMusicPath, count: -1) 32 } 33 34 @IBAction func stop(_ sender: Any) { 35 backmusicPlayer.stop() 36 } 37 38 fileprivate func soundPlayer(player:inout AVAudioPlayer!, path: URL, count: Int) {//here 39 do { 40 player = try AVAudioPlayer(contentsOf: path, fileTypeHint: nil) 41 player.numberOfLoops = count 42 player.play() 43 } catch { 44 print ("error occur") 45 } 46 } 47 48} 49

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

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

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

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

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

TsukubaDepot

2020/03/21 02:07

質問者さんが読まれている本は「たった2日でマスターするiPhoneアプリ開発集中講座 Xcode 11 Swift 5対応」でしょうか? 参考文献はきちんと書かれた方が、より正確な指摘が受けられると思いますので、ぜひご記入をお願いします。 ちなみに(1)については誤植というか、Swiftのバージョンアップに伴う変更だとおもいます。 こちらも出版元に正誤表という形で載っていますので、確認されるといいかと思います。
TsukubaDepot

2020/03/21 02:49

ただ、質問者さんのことを考えると、確かにテキストのこの部分は不親切だと思います。 AVAudioPlayer()でインスタンスを作って、その後またAVAudioPlayer(contentsOf:fileTypeHint:)を代入していますから、なぜこのような処理になるのか疑問に思っても不思議ではないかもしれません。 teratailでもAVAudipPlayer()では議論になっていますし、 https://teratail.com/questions/166981 StackOverflowでも議論になっています。 https://stackoverflow.com/questions/58360765/swift-5-1-error-plugin-addinstanceforfactory-no-factory-registered-for-id-c StackOverflowの記事はこの本の著者も引用していますが、根本的な解決や説明にはなっていないように思います。 (2)については、きちんとした参考書(言語としてのリファレンス)を読んで理解する他にないかと思います。私はC言語の中途半端な知識のままSwiftに触れようとして、結局オプショナル値を理解できず挫折した経験があります。逆にいれば、きちんとSwiftの基礎的なところを押さえておけば、少しはどうにかなるという気もします。 (3)の参照渡しも、今となって思えばテキストの参照渡しの例としてはよくないですね。これも質問者さんが疑問に思われても仕方がないかもしれません。
pegy

2020/03/21 02:58

コメントありがとうございます。ご指摘おっしゃる通りでした。 本文に参考文献を記載させていただきます。
guest

回答1

0

ベストアンサー

Swiftをきちんと学習したのは今年に入ってからなので私だと説明不足のところもあると思いますが、復習もかねて説明します。

ちなみに、参考文献[1]は「荻原剛志著・詳解Swift第5版」、参考文献[2]は「石川洋資、西山勇世著・改訂新版Swift実践入門」からの引用です。

①ここに気がつくまで1日かかってしまったのですが、正直このエクスクラメーションマークの意味となぜこれがないとerrorが生じるのかまでわかりませんでした。変数でクラスや型を指定する際に
player:inout AVAudioPlayer!の様にエクスクラメーションマークが必要なものや
path: URLの様にエクスクラメーションマークが必要でないものがあるのですが、
このエクスクラメーションマークの意味とどの様に要否を使い分けるのかをご教示願えますでしょうか?

変数宣言時に指定するエクスクラメーションマーク(!)は有値オプショナル型(implicity optional value、暗黙的開示オプショナル型とも、参考文献[1]p.105)と呼ばれる値です。

そもそも、オプショナル型とは何かというと、ある特定の型で表せる値以外に nil という特別な値を持たせた型のことです。言い換えれば、値があるか空かのいずれかの値を表す型とも言えます(参考文献[2]p.60)。

ある型(Wrapped型)をオプショナル型として宣言するときには、Optional<Wrapped>と宣言するほかに、Wrapped?と宣言する方法(糖衣構文、シンタックスシュガー、参考文献[2]p.60)もあり、多くは糖衣構文の方が用いられるようです。

swift

1var a : Int // Int型 2var b : Int? // Wrapped<Int>型

上記の例以外も、続く例で引用する例文は Playground で実行できますので、そちらで試されることをお勧めします。

ここで、変数aInt型の変数で10という値を持ちますが、変数bはオプショナル型のInt型(Optinal<Int>)型となります。同じ構文で違う型も宣言することが可能であり、String型のオプショナル型であればString?UIButtonのオプショナル型ならUIButton?と宣言できます。

ところで、Optional型から値を取り出すとき、たとえばInt?型からInt型の値を取り出すためには、開示(アンラップ、参考文献[1]p.97)を行う必要があります。開示を行うことを開示指定といいますが、そのためには!を開示する変数の右側において指定します。

Swift

1var b: Int? // Optional<Int> として宣言 2var c: Int = 20 // Int として宣言 3b = 10 // Optional<Int>に10を代入 4c += b! // d は Int 型で Optional<Int> 型を代入できないのでアンラップする 5 6print(b) 7print(c)

上記の例だと、print(b)の行で Warning が出ます。本来はprint(b!)とアンラップして使うべきなのですが、Optinal型だと分かりやすくするため、あえて開示せずに使っています(参考文献p.98)。ご自身で試されるときにはアンラップした場合も実行して、結果の違いを確かめるといいかと思います。

上記の例でわかるかと思いますが、Int型で定義された変数cに値を代入するためには、Optional<Int>型からInt型を取り出す必要があります。その目的で!を使っています。**重要な点は、オプショナル型を開示するためには、プログラマは!を用いて逐一開示指定を行わなければいけないという点です。**面倒にも思えますが、このようにして nilを持つ可能性がある変数の扱いをプログラマに意識させることが、結果としてバグの少ないプログラムへと繋がる利点もあると言えます。

このとき気を付けなければいけないのは、開示指定の!と、冒頭にのべた有値オプショナル型の!は違う目的である、という点です。混乱してしまいそうですが、重要な点ですので押さえておく必要があります。

さて、話は有値オプショナル型に戻るわけですが、「オプショナル型」である以上、当然これまでの議論も関係してきます。

変数宣言時に!を指定する場合も、?を用いた指定と同じく「オプショナル型」の変数であることには違いがありません。!を用いた宣言と?を用いた宣言の違いは何かというと、!を用いた宣言は、文や式の中で逐一開示指定をする必要がない(しても構わない)という点です。開示指定が不要という点以外は通常の(?で宣言した)オプショナル型と同じです(参考文献[1]p.105)。

では、!で宣言したオプショナル型(有値オプショナル型)と、?で宣言したオプショナル型はどのように使い分けるべきかというと、!で宣言されたオプショナル型は、その後確実に値(インスタンス)が代入され、その後変数の値が nil になることがないような場合に用います。

一番代表的な例は、UILabelなど、インタフェイスのインスタンスを宣言するような場合です。

Swiftのクラスは、すべてのプロパティは明示的に初期されている必要があります(参考文献[1]p.214など)。イニシャライザを使って初期化しない場合には、あらかじめ何らかの値を代入しておく必要があります。

しかし、場合によってはそのような宣言が適当でない場合(という表現が良いかはわかりませんが)もあるので、変数宣言時に明示的に値を代入しない場合、nilが代入されるオプショナル型を使って宣言しておき、後に値(インスタンス)を代入するような場合が多くあります。

もちろん、この宣言で?を使って変数を宣言することも可能ですが、実際は毎回アンラップするのが大変なため、プログラマの責任で!を使って宣言し、アンラップの手間を省いているということもできると言えるでしょう。

適切な例題が思いつかないのですが、たとえば以下のような例があるかと思います。

Swift

1class testA { 2 var a:Int! 3} 4 5var A: testA! 6print(A) // この時点では nil 7 8A = testA() 9print(A) // たとえば Optional(__lldb_expr_27.testA) __lldb_expr_27 は内部名なので環境によって変わる 10 11print(A.a) // この時点では nil 12A.a = 10 13print(A.a) // この時点で Optional(10)

なぜ変数宣言をオプショナル型で行わなければならないのかという問題は、実はクラスとイニシャライザによるプロパティの振る舞いも関連してくるはずのですので、適切な参考書して理解を進めるのがいいかと思います。

②また、関連してですがテキストによって、変数の設定の仕方について
var cymbalPlayer: AVAudioPlayer!//here3
の様にする場合と
var cymbalPlayer = AVAudioPlayer()
と書いている様なものもあるのですが、どちらでも良いのでしょうか?エクスクラメーションマークとも関連があるかもしれないのでお尋ねさせていただきました。

結論から言いますと、どちらでも構わないと思います。

たしかに、var cymbalPlayer = AVAudioPlayer()の例だと Xcode 11.3 では実行時エラーになりますが、Xcode 11.4 では問題なくコンパイルが通りますし、実行時エラーを起こすこともありませんでした。

コメントで触れましたように、teratail の過去問や Stack Overflow でもこの辺りの問題について議論されていたようですが、あまり深く考えなくてもいいかと思います。

var cymbalPlayer: AVAudioPlayer!は先に変数だけ宣言しておいて、後からインスタンスを代入していますし、var cymbalPlayer = AVAudioPlayer()では変数宣言と同時にインスタンスを代入しているだけの違いです。

Swift

1var a: Int 2a = Int(10) // a = 10 とおなじ。 3print(a)

上記の例(例-a)だと、まず変数をInt型で宣言しておき、その後aInt型のイニシャライザを用いて10を代入している例です。

これは、

Swift

1var a: Int = Int(10) 2print(a)

と同じ理屈だと理解して頂けるのではないでしょうか。

③また、少し話が逸れてしまい申し訳ないのですが、
soundPlayer(player: &cymbalPlayer ,path: cymbalPath, count: 0)//here2の様に
参照渡しを利用する場合なのですが、これを&を使用せずに値渡しすべきではない理由、またはその弊害が> ピンと来ないのですが、テキストには
"「soundPlayer」処理後にシンバルの情報を呼び出し元に戻して利用したいので、参照渡しにしている"
と記載されています。実際には&なしでコーディングしても結果、動作させても影響がない様に思えるのですが、具体的にどの様な影響があり、なぜ参照渡しにすべきなのかアドバイスをを頂戴できると大変嬉しいで す。

私が少し勘違いと混乱していたようです。
&を取ってコンパイルするとエラーになるはずですが、質問者さんの環境ではいかがでしょうか。

&の不足

したがって、具体的な影響としては、まず「コンパイルに通らない」という影響があります。

理由ですが、英語による Swift のマニュアルのうち、In-Out Parametersの項をみるとこのように記述してあります。

You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.

また、参考文献[1]では「ここで使っているという記号は通常の演算子ではなく、inout引数に対応する実引数で表す記法です」とあり(p.44)、参考文献[2]では「インアウト引数を持つ関数を呼び出すには、インアウト引数の先頭に&を加えます」とあります(p.110)。

C言語をやっていたらアドレス演算子としての印象が強く、&という記号そのものに何らかの操作があるように感じます(し、私の場合はついその印象で考えてしまいました)。

しかし英文マニュアルの説明などを見る限り、Swiftでは&を付与することによって特別な操作があるわけではなく、&をつけることによって、引数として与えられた変数が書き換えられるということを明示的に示していると考えられます。

では、そもそも何故値渡しではなく&をつけて参照渡しするのかというと、処理先の関数の中で実引数として渡したプロパティが参照しているインスタンスそのものを書き換えているからです。

あんまり良い例題ではないですが、こんなサンプルを作ってみました。

swift

1import UIKit 2 3class ClassA { 4 var msg: String 5 init(msg: String) { 6 self.msg = msg 7 } 8 deinit { 9 print("deinit - msg: (msg)") 10 } 11} 12 13func newClass(cl: ClassA){ 14 // Swift は仮引数の値は書き換えられないので、同名の別の変数に置き換える 15 // 参考文献[1]p.46 16 var cl = cl 17 cl = ClassA(msg: "newClass内で作ったインスタンス") 18} 19 20func newClassWithInout(cl: inout ClassA){ 21 cl = ClassA(msg: "newClassWithInout内で作ったインスタンス") 22} 23 24do{ 25 print("inout 無しで呼び出し") 26 var clA = ClassA(msg: "最初のインスタンス") 27 print("newClass呼び出し前 - msg: (clA.msg)") 28 newClass(cl: clA) 29 print("newClass呼び出し後 - msg: (clA.msg)") 30} 31 32print("") 33 34do{ 35 print("inout 付きで呼び出し") 36 var clA = ClassA(msg: "最初のインスタンス") 37 print("newClass呼び出し前 - msg: (clA.msg)") 38 newClassWithInout(cl: &clA) 39 print("newClass呼び出し後 - msg: (clA.msg)") 40}

実行すると、こんな感じになると思います。

inout 無しで呼び出し newClass呼び出し前 - msg: 最初のインスタンス deinit - msg: newClass内で作ったインスタンス newClass呼び出し後 - msg: 最初のインスタンス deinit - msg: 最初のインスタンス inout 付きで呼び出し newClass呼び出し前 - msg: 最初のインスタンス deinit - msg: 最初のインスタンス newClass呼び出し後 - msg: newClassWithInout内で作ったインスタンス deinit - msg: newClassWithInout内で作ったインスタンス

注意深く読まないと分かりにくいとおもいますし、もっと細かくprint()をいれて動作を確認する必要はあると思いますが、inoutあり無しの処理において、最初に作ったインスタンスと、関数内で新たに作ったインスタンスのライフサイクルの違いはわかるかと思います。

投稿2020/03/21 14:37

編集2020/03/21 16:08
TsukubaDepot

総合スコア5086

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

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

pegy

2020/03/21 16:29

TsukubaDepot様 一旦、御礼のコメントをさせてください。 また、貴重なお時間をいただき、初心者の私に丁寧な説明と確認までいただいてしまい、あわせてお詫びを申し上げたいと思います。 今自分でも①に関連してオプショナル型という概念を調べ出していたので、非常にご説明と例示がわかりやすくとても助かっております。 しかしながら、全てを咀嚼し、また動作を細かくpraygroundで確認するには相当の時間を要するため、一旦御礼とお詫びに留めさせていただき、ガッツリ今からチェックしていきたいと考えております。 この様な場合、案件を一旦closeするべきかはわからないため、openとさせていただき、作業を進めて参ろうと思います。 よろしくお願い申しあげます。
pegy

2020/03/21 16:31

一点だけ、コメントで気になった点があるとすれば、自分の11.3の環境では以下の言及箇所について、コンパイルエラーにならず通っていたので、この点はまた確認して、追ってコメントを残したいと考えております。 (前略)"たしかに、var cymbalPlayer = AVAudioPlayer()の例だと Xcode 11.3 では実行時エラーになりますが、"(後略)
TsukubaDepot

2020/03/21 16:34

私も不確実なところがあるので良い復習になりました。 質問者さんの過去の質問を拝見したところ、hoshi-takanoriさんが私が引用した文献を勧めていらっしゃったのを目にしました。 2冊とも良い本なのですが、言語仕様の詳しさに限れば荻原先生の「詳解Swift第5版」が良いと思うので、もし手に取る機会があれば一度ご覧になるのが良いかもしれません。 質問をcloseされるのはいつでも大丈夫ですし、ツッコミがあればいつでもお受けしますので、気になったところがあればご質問ください。
TsukubaDepot

2020/03/21 16:40

私が持っている「iPhoneアプリ〜」は初版第一刷でした。もしかしたら第二刷だと修正がいろいろ入っていて、ソースコードの記述も細かい点で違うのかもしれません。 Xcode11.3というのは、もしかしたら記憶違いかもしれません。ただ、ネット上にある正誤表だとXcode11.0 ではインスタンスを作るあたりでエラーになっていたようなので、この前後のバージョンではエラーになっていたのかもしれません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問