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

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

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

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

Q&A

解決済

2回答

5572閲覧

Array index out of rangeの解決。

UsagiPerry

総合スコア19

Swift

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

0グッド

0クリップ

投稿2017/05/26 17:34

###前提・実現したいこと
swiftでクイズアプリを作成しています。各問題では選択肢ボタンを押すと次の問題に移行するシステムにしています。時間制も設けており同じく制限時間を過ぎると次の問題に移行します。
問題なのが、3,4問(タイミングはランダム)を過ぎてから、ボタンを押すor制限時間を越えるとアプリが落ちてしまいます。
###発生している問題・エラーメッセージ

fatal error: Array index out of range

既存のアプリを参考に作成しており、それとの変更点は2つあります

①クイズの画面でされるtextviewを1→6つに増やしている。
csvファイルのテキストデータは、","ごとに設問、正解番号、選択肢の順に設定しているのですが
###csvファイル変更前

一般的に胃腸の働きを弱めてしまう効果があるので、「天ぷら」と食べ合わせが良くないとされている食べ物は次のうちどれか?,1,スイカ,梅干し,ゆで卵,ソーセージ

上記の設問に当たるところを5つ増やし、連想クイズのようなものにしています
###csvファイル変更後

ゴリラ,りんご,黄色,朝ごはん,皮,スリップ,2,卵焼き,バナナ,鶏肉,雨

csvファイルの","で区切られた要素はtargetProblemという配列に格納されていたため、その配列に新しく作ったtextviewを5つ、targetProblem[1]〜[5]として宣言し、解答番号と選択肢のインデックスはそれぞれ5ずつ後ろにずらしました

###該当のソースコード

swift

1@IBOutlet var keyword: UITextView! //textviewを5つ分増やす 2@IBOutlet var keyword2: UITextView! 3@IBOutlet var keyword3: UITextView! 4@IBOutlet var keyword4: UITextView! 5@IBOutlet var keyword5: UITextView! 6@IBOutlet var keyword6: UITextView! 7 8keyword.text = targetProblem[0] as! String //targetProblem[1]〜[5]にkeyword2〜6.text 9keyword2.text = targetProblem[1] as! String 10keyword3.text = targetProblem[2] as! String 11keyword4.text = targetProblem[3] as! String 12keyword5.text = targetProblem[4] as! String 13keyword6.text = targetProblem[5] as! String 14 15 16//選択肢であるtargetProblem[2,3,4,5]→[7,8,9,10] 17answerButtonOne.setTitle("1." + String(describing: targetProblem[7]), for: UIControlState()) 18answerButtonTwo.setTitle("2." + String(describing: targetProblem[8]), for: UIControlState()) 19answerButtonThree.setTitle("3." + String(describing: targetProblem[9]), for: UIControlState()) 20answerButtonFour.setTitle("4." + String(describing: targetProblem[10]), for: UIControlState()) 21 22 23 24

###バグの原因
Thread1にはkeyword2.text = targetProblem[1] as! StringがEXC_BREAKPOINTだと述べてありましたが、なぜこの宣言が問題なのかわかりません。

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

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

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

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

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

Bongo

2017/05/26 17:55

Array index out of rangeとなると、targetProblemにデータを格納する部分のコードがどうなっているか気になります。また、確認しますが、最初のうちは選択肢ボタンを押したり制限時間を超えたりすれば正しく次の問題に移るが、たとえ同じ問題CSVで同じように実行しても数問過ぎるとクラッシュし、しかもクラッシュするまでの問題数もバラバラ、ということでよろしいでしょうか。
guest

回答2

0

まず、fatal error: Array index out of rangeとは何か、から。

swift

1let array = [0, 1, 2, 3] // 要素数は4 2 3let e = array[8] // 9番目の要素にアクセス ... (a)

(a)の箇所でfatal error: Array index out of rangeが発生します。
(要素数 - 1)を超えたインデックスを指定しているためです。

つまり、あなたの場合ですと targetProblem の要素数が 1 のためクラッシュしているということです。

対処療法的ではありますが、

swift

1// 要素数が11であるかをチェック 2if targetProblem.count != 11 { 3 // エラー処理 4}

のような処理をどこかに入れてください。


---余計な話---
上の回答は対処療法でしかありません。
問題を [Any] のような形で保持せず

siwft

1struct Quiz { 2 let hints: [String] 3 let choices: [String] 4 let answer: Int 5 6 init?(hints: [String], choices: [String], answer: Int) { 7 guard hints.count == 6, 8 choices.count == 4, 9 0..<5 ~= answer 10 else { return nil } 11 self.hints = hints 12 self.choices = choices 13 self.answer = answer 14 } 15}

のような専用の型を用意して安全に利用できるようにすべきです。

投稿2017/05/27 08:27

編集2017/05/27 08:28
MasakiHori

総合スコア3384

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

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

UsagiPerry

2017/05/27 09:37

丁寧にありがとうございます。 問題番号を扱う、配列problemArrayの引数をシャッフルする操作をコメントアウトしたところ一時的にArray index out of rangeの問題は解決しました。しかしおっしゃる通りguardを使った方が別のクラッシュも起きづらいので、そうさせていただきます。 ``` do { //CSVデータを読み込む var csvData: String = try String(contentsOfFile: csvBundle!, encoding: String.Encoding.utf8) csvData = csvData.replacingOccurrences(of: "\r", with: "") //改行を基準にしてデータを分割する読み込む let csvArray = csvData.components(separatedBy: "\n") //CSVデータの行数分ループさせる for line in csvArray { //カンマ区切りの1行を["aaa", "bbb", ... , "zzz"]形式に変換して代入する let parts = line.components(separatedBy: ",") problemArray.add(parts) } //配列を引数分の要素をランダムにシャッフルする(※Extension.swift参照) problemArray.shuffle(self.problemArray.count) } catch let error as NSError { print(error.localizedDescription) } } ``` 上のコードはcsvファイル(改行が問題番号を次に進ませるサイン)から問題番号を取得したproblemArrayに関するものです。 先述した通り problemArray.shuffle(self.problemArray.count)を行うと問題の途中でArray index out of rangeが原因のクラッシュが起きてしまうのですが、原因についてわかることがあれば教えていただけないでしょうか。 shutffleなしでは全問題表示できたため、csvファイルには不正な行はないと考えております。
MasakiHori

2017/05/27 11:58

回答の後半部分は無視してください 僕以外の人が何がわからないのかが僕にはわかりません。 僕の回答のどこがわからなかったか教えてもらえませんか? fatal error: Array index out of range がどのような時に発生するのかはわかりましたか? if targetProblem.count != 11 { を使うとどのような問題が解決されるのかはわかりましたか?
guest

0

ベストアンサー

さきほど問題格納部分が気になると申し上げましたが、参考とされたのはこちらの方の製作例でしょうか?
もしそうであれば、この場合setProblemsFromCSVが格納部分のようですが、問題格納後にproblemArrayをシャッフルしております。ということは、もしCSV内に不正な行がある場合、ランダムなタイミングでその問題に到達した際にクラッシュする、ということもありそうに思われました。
problemArrayにCSVからデータを取り込み終えたら、problemArrayの内容を確認してみるといいかもしれません。

[「shutffleなしでは全問題表示できたため、csvファイルには不正な行はない」とのご見解について追記]
文章だけで意図を伝えるのはなかなか難しいですね...
データ自体は全問分だけありますので、全問表示できるのは確かでしょう。私がご確認いただきたかったのは、CSVファイルをXcodeで開いた時に

こうなるはずが

こうなるはずが

こうなって

こうなってしまっていないかということなんです...

投稿2017/05/26 18:18

編集2017/05/27 12:29
Bongo

総合スコア10807

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

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

UsagiPerry

2017/05/26 18:35

解答ありがとうございます。はい、そのリンクの方のアプリをフォーマットにしています。 csvファイルの方を一旦全てリセットして内容を a,a,a,a,a,a,2,a,a,a,a b,b,b,b,b,b,2,b,b,b,b … m,m,m,m,m,m,2,m,m,m,m に変えて実装してみました。今回は9問目までは行きましたがそこで同じくバグが発生しました。二回目は2問目でバグりました。また、バグる際に選択肢が正しいか間違っているかは関係ないようです。1回目は正解と2回目は不正解でアプリが落ちました
Bongo

2017/05/26 20:47 編集

おそらく、厳密に言えば、たとえば2問目でクラッシュした場合、実際に問題となるのはその次の3問目(として表示される予定だった問題)になるかと思います。可能性としてはCSVの末尾に余分な改行があって、空の問題ができてしまった...などでしょうか。 self.problemArray.shuffle(self.problemArray.count)をコメントアウトして、同じ順序で問題を出させると、クラッシュする位置が毎回同じになったりするでしょうか? [追記] もし余分な改行などが原因の場合、予定している問題数(最後がm,m,m...ということは全13問でしょうか?)とproblemArray.countの数が異なっていると予想されます。
UsagiPerry

2017/05/27 07:30

self.problemArray.shuffleをコメントアウトすると毎回最後の問題までいったのち解答が終わった後バグが起きるようになりました。 BREAKPOINTはScoreController.swiftの let nibDefault: UINib = UINib(nibName: "scoreCell", bundle: nil) となっていました。 関連性はおそらくないですが修正部分が他に思いつかなかったため、 ViewController.swiftの //テーブルビューに関係する定数 struct GuidanceTableStruct { static let cellCount: Int = 5 static let cellSectionCount: Int = 1 } のcellCountを5→10(問題数分)に増やしたところ起動時からクラッシュが発生しました。
UsagiPerry

2017/05/27 08:15

QuizControllerの画面からScoreControllerの画面に移行する際にクラッシュが起きることに関しては、以前ストーリーボードを色々といじっていた際に元の構造がわからなくなったため、(command+zではなく、)ScoreControllerSceneを消して、取っていたバックアップから元のScoreControllerSceneをコピペしたのが原因ではないかと考え始めました。 コードとの間にOutletsがある場合などは一度消してしまうともう一度繋げ直したりしないとダメでしょうか?
Bongo

2017/05/27 09:38

「毎回最後の問題までいったのち解答が終わった後バグが起きるようになりました」とのことなので、いよいよCSVファイルの末尾がおかしい可能性が濃厚ですね。CSVファイルのm,m,m,m,m,m,2,m,m,m,m←この最後のmの次に改行がありませんでしょうか? MasakiHoriさんからご説明がありましたが、ご提示の「該当のソースコード」ではtargetProblem[0]〜targetProblem[10]までの11個の値があることを前提としております。つまり、Array index out of rangeが出るということは、targetProblem配列の要素数が足りないのだろうと予想されます。そういった正常でないデータの混入を防ぐ措置が必要になります。 ひとまずはなんとかプロジェクトをご質問投稿時の状態まで復旧して、さしあたりArray index out of rangeに関係するtargetProblemやproblemArray周りの修正に注力された方がよさそうです。 なお、アウトレット等の接続状況は、ストーリーボードを開いて、ユーティリティエリア(画面右端の領域)にあるコネクションインスペクタ(領域上端にある、○の中に→マークのボタン)で確認できます。
UsagiPerry

2017/05/28 01:24

追記ありがとうございます… csvは前者の状態になっています。
Bongo

2017/05/28 02:50

確認ありがとうございます。なるほど、CSVの最後は...m,m,mで終わっていますか... 「シャッフルすると途中でクラッシュ」「シャッフルしないと最後でクラッシュ」というところからCSVの最後を疑ってみたのですが、どうやら見立てがハズレだったようです。 ひとまずMasakiHoriさんのアドバイスをご参考に、keyword.text = targetProblem[0] as! Stringの前か、targetProblemにproblemArrayから配列を入れた後に、targetProblem.countのチェックを入れて対処する方向でいかがでしょう。
UsagiPerry

2017/05/28 08:04

そうですね。シャッフルするとクラッシュする件はその方の記述を参考に手立てを打とうと思います。 プロジェクトとして前には進む訳ではないですが、ひとまず今はアプリを起動して無事最後の画面までいくという動作を確認したいと思っています。 よろしければ以下の疑問にもお付き合いいただけないでしょうか。 現在10問目が終わるとクラッシュする、という状況は変わっておりませんが ScoreControllerView.swiftの //テーブルビューのデリゲート設定 resultHistoryTable.delegate = self ¥¥¥¥ resultHistoryTable.dataSource = self ¥¥¥¥¥ の部分がBREAKPOINTとなっています。 ストーリーボードのresultHistoryTableを繋ぎ直しましたが結果は変わりませんでした。
Bongo

2017/05/28 11:50

EXC_BREAKPOINTは、Swiftがfatal error発生時にアプリをクラッシュさせるために発したシグナルのようです。クラッシュ時点でどの位置で止まっているかはひとまず気にせずに、fatal error: Array index out of rangeを消す作業を進められるとよいでしょう。 もし、unexpectedly found nil while unwrapping an Optional valueのような「nil」などの単語が入ったfatal errorが出るようでしたら、そちらの方はストーリーボードとコードの間の接続不良が原因かもしれません。
UsagiPerry

2017/05/29 06:47

shutffleなし、の状態でiPhoneに対する1度目のデバッグは必ず最後の問題でクラッシュするのですが、左上の四角いボタン(Stop the running the scheam or application)を押した後iPhoneの方でアプリを起動させると最後の結果画面までうまく表示されました。 このシチュエーションはよくあるのでしょうか?原因がわかりません。 また、一応ゆるゆるながら実装はできたのでshutffleのArray index out of rangeの問題に立ち返っています。 上の方のコードを参考に struct Quiz { let hints: [String] let choices: [String] let answer: Int init?(hints: [String], choices: [String], answer: Int) { guard hints.count == 6, choices.count == 4, 0..<5 ~= answer else { return nil } self.hints = hints self.choices = choices self.answer = answer } } Quiz.hints = self.problemArray[self.counter] as! String Quiz.choices = self.problemArray[self.counter] as! String Quiz.answer = self.problemArray[self.counter] as! int といったおそらく無茶苦茶なコードを書いているのですが、 csvファイルの","区切りでヒント、正解番号、選択肢がインプットされている構造をtargetProblemのような一括管理の配列ではなく、上のhintsやanswerといった別々のもので扱うにはどういった記述が必要でしょうか(日本語下手ですみません)。hintsやanswerはNSArrayの型でなくても良いのでしょうか。
Bongo

2017/05/29 08:19

前半部について、一旦デバッグを中止してからあらためて起動したということは、デバッガーがアタッチされていない(動作中に発生したもろもろのエラーを捕捉できる状態でない)状態で動かしていることかと思います。確かに一見正常に動いているようでも、内部的には異常な動作をしていて、後々の不具合の原因になる恐れがあります。デバッガーの捕捉したバグは一通り潰しておくべきでしょう。 後半部について、MasakiHoriさんが例示されたQuiz型は、一つ一つの問題を表す型として使うことを想定されたものと推察されます。つまり、現状problemArrayは「文字列の配列の配列」になっていますが、これを「Quizオブジェクトの配列」に修正し、CSVから問題を読み込む際には ・CSVから一行ずつ文字列を取り出す。取り出した行について... ・・行をカンマで分割し、さらに個別の文字列に分ける。 ・・これらから、hints(連想のヒントとなる文字列の配列)、choices(回答選択肢となる文字列の配列)、answer(正解番号を表す整数)の3つを作る。 ・・Quizオブジェクトを、これらhints、choices、answerを引数にして作る。このオブジェクトが一つの問題を表す。なお、Quizオブジェクト生成時にはhintsの数、choicesの数、answerの範囲がチェックされ、これらが正しくなければnilを返す。nilならばそれは問題として不正なので、problemArrayには追加しない。 ・・Quizオブジェクトが正常に作られていたら、それをproblemArrayに追加する。 ・全ての問題を追加し終えたら、problemArrayをシャッフルして順序をバラバラにする。 ...と、このような感じでしょうか。MasakiHoriさんも「余計な話」とおっしゃっていますが、それぞれのオブジェクトがどういう役割を担うかを十分イメージしてからでないと、奇妙な実装になってしまうかもしれません。しかしゆくゆくはこういった設計に改修すれば、より堅牢なアプリになるかと思います。 今のところは無理にQuiz型を導入せず、エラーなしで動作するようになってからあらためて検討するのもアリではないでしょうか。
UsagiPerry

2017/05/29 18:50

丁寧にありがとうございます。参考に実装を試みます。 "今のところは無理にQuiz型を導入せず、エラーなしで動作するようになってからあらためて検討するのもアリではないでしょうか。" →なるほど… 正直エラーなしで解決(Array index out of rangeによるクラッシュの回避)する方法が浮かばなかったので、Quiz型に乗っ取るのが最善策と考えていました。
Bongo

2017/05/29 20:25 編集

いえ、そう難しく考える必要はないんじゃないでしょうか。 場当たり的ですが、たとえば「keyword.text = targetProblem[0] as! String」の前の行に「if targetProblem.count == 11 {」を入れ、「answerButtonFour.setTitle("4." + String(describing: targetProblem[10]), for: UIControlState())」の次に行に「}」を入れてみるとか、別の案として、CSVからの読み込み部分の「let parts = line.components(separatedBy: ",")」と「problemArray.add(parts)」の間に「if (parts.count != 11) continue」を入れてみるとか... [追記] 前者の案は、あれだけではダメでしたね。どこか別の場所で「targetProblem[6]」を使っている箇所があるかと思いますが、そこもifで囲ってやらないと、そちらでクラッシュすると思います。先に後者の案から試されてはいかがでしょう。
UsagiPerry

2017/05/31 08:14

返信ありがとうございます。 " CSVからの読み込み部分の「let parts = line.components(separatedBy: ",")」と「problemArray.add(parts)」の間に「if (parts.count != 11) continue」を入れてみるとか... " →実装すると、問題がシャッフルした状態で出題されました。ありがとうございます。 相変わらず1回目のビルドでは1問目も開けませんが。 QuizControllerの var csvData: String = try String(contentsOfFile: csvBundle!, encoding: String.Encoding.utf8) がbreakpointとなっています。コンソール画面にはArray index out of rangeのようなクラッシュのメッセージはなくただ 11db と表示された状態です。 何か手立てはないでしょうか?(質問しっぱなしでたいへん申し訳ありません) 個人的にはcsvファイルを、mBaaSというアプリのデータ管理を安全に行ってくれる外部のサービスに置いて、そこから取得しようかと考えています。(おそらく効果はないでしょうが)
Bongo

2017/05/31 09:01

念のため確認しますが、var csvData: String = try String(contentsOfFile: csvBundle!, encoding: String.Encoding.utf8)の行にブレークポイントは設定されていたりしますか?Xcodeのエディタ画面の行の左端に、青いマーカーのようなものがあるでしょうか。
UsagiPerry

2017/05/31 13:31

返信ありがとうございます。 ご指摘の通り、breakpointが設定されていました。breakpointとは、クラッシュの原因となる行を表しているものと勝手に勘違いしておりました。 shutffleありでの実装が、最初のビルドでできるようになりました。初心者のわかりづらい質問にここまでお付き合いいただき、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問