質問: tableData[section] return self.tableData.count } } ```
回答: 簡単に言うと、Swiftではインスタンスプロパティの初期化式に他のインスタンス変数(全て`self`の参照、とみなされます)を使用することはできません。 ### 原因となるSwift言語独特の事情 詳しくはSwift本(The Swift Programming Language (Swift 3.0.1))のこちらを熟読してもらわないといけないのですが、長文の英語なので要点をまとめると、Swiftの(クラス)イニシャライザでは、次のことを指定の順序で行わないといけません。 1. 自クラスで導入した全てのインスタンスプロパティに初期値を与える (プロパティに初期化式を与えてもいいし、自前の`init`の中で代入しても良い) 2. イニシャライザの中でスーパークラスの指定イニシャライザを呼ぶ (イニシャライザを継承すると自動的に呼んでくれるし、自前のイニシャライザの中で明示的に`super.init(...)`で呼んでも良い) とここまでは、他のオブジェクト指向言語にも似たような規則があるわけですが、Swiftではここにさらに厳しい制約があって、 * **上記の1.2.が済むまでは`self`にアクセスしてはいけない** と言うことになっています。従ってイニシャライザよりも前に評価される初期化式の中では、(`self`経由でアクセスされる)他のインスタンスプロパティを使うこともできなければ、インスタンスメソッドを呼ぶこともできない、と言うことになります。 (ちなみに上の1.2.がSwift用語で言う「2フェーズイニシャライズ」のフェーズ1と言うことになっています。) ### 対処方法 従って他のインスタンスプロパティの値を使用するような(あるいはインスタンスメソッドの結果を使用するような)値で初期化したい場合は、以下のような対応方法があります。 1. 自前のイニシャライザの中で`super.init(...)`を読んだ後に値を与える 上記の事情により初期値を与えないと`super.init(...)`は呼べませんから、初期化式等で仮の値を与え(場合によってはそのために変数のデータ型をImplicitly Unwrapped Optionalにしないといけないかもしれません)、それを`super.init(...)`の後で書き換える、と言う形になります。 あなたのコードのように普通は自前のイニシャライザを定義したりしないViewControllerの場合には、イニシャライザを定義するだけで面倒なので、このパターンは使いにくいかもしれません。開き直って、`viewDidLoad()`の中で初期化すると言うのは大変よく見られるパターンです。 2. `lazy`変数を使用する 上記の制約からは例外に当たるのですが、`lazy`宣言されたインスタンスプロパティについては、初期化式の中で、他のインスタンスプロパティを含むことができます。`lazy`プロパティの初期化は、「初めてそのプロパティが参照される直前(従ってイニシャライザの実行より後)に実行される」「`super.init(...)`の前には実際には初期化されていないのだが、参照する際には必ず初期化されるので、未初期化のままの値が使われる可能性はない」ことから例外扱いされているのでしょう。 3. 参照されているプロパティをクラス定数かトップレベル定数にしてしまう クラスプロパティやトップレベルの変数・定数はクラスのイニシャライザが呼ばれるより前に初期化が完了しているので、クラス定義の中でインスタンスプロパティの初期値として問題なく使えます。 ### あなたのコードの場合は 1.の「`viewDidLoad()`の中で初期化する」を採用するのであれば、`tableData`のプロパティ宣言は ``` var tableData: [[String: Bool]] = [] ``` のようにしておいて、 ``` override func viewDidLoad() { super.viewDidLoad() tableData = [checkListItem1, checkListItem2, checkListItem3, checkListItem4, checkListItem5] // UITableView の作成 tableView.frame = CGRect( x: 0, y: statusBarHeight, width: self.view.frame.width, height: self.view.frame.height - statusBarHeight ) tableView.delegate = self tableView.dataSource = self self.view.addSubview(tableView) } ``` のようになるでしょうか。 2.の`lazy`を使用するなら、書き換えるのはこの1行になります。 ``` lazy var tableData: [[String: Bool]] = [self.checkListItem1, self.checkListItem2, self.checkListItem3, self.checkListItem4, self.checkListItem5] ``` 各プロパティの参照に`self`が必要なのは、`lazy`プロパティの初期化式が実はクロージャーの一種ということを考えれば納得がいくのですが、`self.`を付け忘れた時のエラーメッセージがいまいちわかりにくいです。ご自身で確かめてください。 3.のパターンでも書き換えができますね。`checkListItem1`〜`checkListItem5`をクラスの外に追い出してトップレベル定数としてしまいます。 ``` // チェックリストの項目とチェック状態 private let checkListItem1: [String : Bool] = [ "アイテム1" : true, "アイテム2" : false, "アイテム3" : true, "アイテム4" : true, "アイテム5" : false ] private let checkListItem2: [String : Bool] = [ //... ] private let checkListItem3: [String : Bool] = [ //... ] private let checkListItem4: [String : Bool] = [ //... ] private let checkListItem5: [String : Bool] = [ //... ] class ViewController: UIViewController { //... var tableData = [checkListItem1, checkListItem2, checkListItem3, checkListItem4, checkListItem5] //... } ``` チェック状態を表す`Bool`値は書き換えられるので`let`定数にはできないと思われるかもしれませんが、Swiftでは`Array`も`Dictionary`も値型なので、`tableData`には`checkListItem1`〜`checkListItem5`の内容が初期値としてコピーされるだけで、`tableData`さえ`var`で宣言されていれば、`checkListItem1`〜`checkListItem5`の方は`let`でも問題ありません。別クラスから`ViewController`のそれらインスタンスプロパティをいじる、なんてことはしないというのが前提ですが。 ### さらに 確実で簡単なのは最初の方法でしょうか。お試しください。 で、表題のエラーを解消すると、あなたのコードの中に潜むエラーが芋づる式に顔を出すようですが、そちらはまた別件ということで、ご自身で解決できない部分が残ればまた別スレでご質問いただいた方がいいと思います。