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

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

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

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

Q&A

解決済

1回答

514閲覧

Swift tableviewやcollectionviewの生成時にクロージャを使う意味

samson66

総合スコア35

Swift

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

0グッド

0クリップ

投稿2020/10/01 02:28

たびたび、簡単なコード(tableviewやcollectionviewを生成するだけ)でクロージャを使うを見かけることがあるのですが、一体どんなことに役立つのでしょうか?
本などを調べてみてもあまり参考にならなかったのでこちらで質問させていただきますm(_ _)m

ただ単に複雑になりがちなtableviewの細かい設定などが見やすくなるとかでしょうか?
また試しに使ってみたところoverride func viewDidLoad()の外で記述すると警告文が出てきて実行できません。
viewDidLoad()内で記述するなどの決まりがあるのでしょうか?

//スクリーンの横幅、縦幅を定義 let screenWidth = Int(UIScreen.main.bounds.size.width) let screenHeight = Int(UIScreen.main.bounds.size.height) let collectionView: UICollectionView = { //セルのレイアウト設計 let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() //各々の設計に合わせて調整 layout.scrollDirection = .horizontal layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 let collectionView = UICollectionView( frame: CGRect(x: 0, y: 0, width: screenWidth * 0/100, height: screenHeight ), collectionViewLayout: layout) //エラー:Instance member 'screenHeight' cannot be used on type 'ViewController'; did you mean to use a value of this type instead? collectionView.backgroundColor = UIColor.white //セルの登録 //collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell") return collectionView }()

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

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

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

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

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

guest

回答1

0

ベストアンサー

とりあえず、後半のご質問である「また試しに使ってみたところoverride func viewDidLoad()の外で記述すると警告文が出てきて実行できません。」への回答です。

このエラーがが出る理由ですが、(おそらく)screenWidthscreenHeight の宣言を「クラスのトップレベル(内部)」で行なっていることだと思います。

クラスのトップレベルにおけるプロパティ(変数)は、全てのプロパティの初期化が終了するまで(実質、イ全てのイニシャライザが呼び出され、インスタンスが生成されるまで)、相互に参照することができません(計算型プロパティなど、例外もあります)。

どういうことかというと、

Swift

1 let screenWidth = Int(UIScreen.main.bounds.size.width) 2 let screenHeight = Int(UIScreen.main.bounds.size.height)

と宣言されていますが、これら2つの変数は続くクロージャ内部での処理

Swift

1 2 let collectionView = UICollectionView( frame: CGRect(x: 0, y: 0, width: screenWidth * 0/100, height: screenHeight ), collectionViewLayout: layout) 3 //エラー:Instance member 'screenHeight' cannot be used on type 'ViewController'; did you mean to use a value of this type instead?

で呼び出されているため、表記のようなエラーが出てしまいます。

ここで再度、参考にされたQiitaの記事を見ていただきたいのですが、screenWidthscreenHeight ともにクラス外部のグローバル変数として宣言されていることに気づかれるかと思います

Swift

1let screenSize: CGSize = CGSize(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height) 2 3final class ViewController: UIViewController { 4 5 private let collectionView: UICollectionView = { 6 // 中略 7 let collectionView = UICollectionView( frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height ), collectionViewLayout: layout) 8 collectionView.backgroundColor = UIColor.white

この場合にはエラーが出ません。なぜかというと、ViewControllerというクラスを宣言する前に、グローバル変数として既にscreenSizeが決定してしまっているからです。

なので、グローバル変数にすれば全て解決かというと、それほど単純な問題でもありません。
一般的に、グローバル変数を利用することによって、プログラム全体の見通しが悪くなる可能性があるためです。

ここでいう「見通しが悪くなる」というのは、「グローバル変数がどこで定義されたものか分からなくなってしまう」という意味です。クラスのプロパティとして定義してあれば、宣言したクラスやその拡張(extension)をみればわかりますが、グローバル変数としてしまうと、最悪関連する全てのファイルを探し回る必要も出てきてしまいます。

そこで、クラス内部のトップレベルで定義したプロパティを、別のトップレベルで宣言したプロパティ(やクロージャ)で使うための方法として使われる一つの方法が「遅延格納型プロパティ(lazyキーワード)」です。

遅延格納型プロパティというのは、クラスのトップレベルで宣言するものの、実際にその値が評価されるのは実際に使われる段階になってからです。実際に使われる段階だと、インスタンスは既に生成されている状態で、全てのプロパティに初期値が入っていることが保証されている(というか、保証できるように自分でクラスを設計する)ため、問題とならないわけです。

具体的にはこのような感じで宣言し、使います。

Swift

1class ViewController: UIViewController { 2 //スクリーンの横幅、縦幅を定義 3 let screenWidth = UIScreen.main.bounds.size.width 4 let screenHeight = UIScreen.main.bounds.size.height 5 6 // lazyキーワードを付与した上、let ではなく var として宣言する 7 lazy var collectionView: UICollectionView = { 8 // 中略 9 let collectionView = UICollectionView( frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight ), collectionViewLayout: layout) 10 // 攻略 11 return collectionView 12 }()

このような方法を取れば、コンパイル時にエラーが出てくることはなくなります(が、遅延評価であることを考慮しなければ、予想外の結果がでるかもしれません。それについてはぜひいろいろ実験してみてください)。


「簡単なコード(tableviewやcollectionviewを生成するだけ)でクロージャを使う例を見かけることがあるのですが、一体どんなことに役立つのでしょうか」ということについては、私は適切な答えを持ち合わせていないため、他の方のご意見も伺った方がいいかと思います。

一つ考えられるのは、インスタンスを代入させたいプロパティの宣言と、そこで実際に処理させたい内容を同時に記述させることができるため、全体の処理が見やすくなる、ということでしょうか。

投稿2020/10/01 14:58

TsukubaDepot

総合スコア5086

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

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

samson66

2020/10/02 07:59

授業料を出したいほど、詳しい解説にただただ感謝です。 遅延格納型プロパティは確かにクロージャを使っているコードで見かけてはいたのですが、今まで使い所が分からず無視していました...。 グローバル変数は以前からあまり使わない方がいいという事はぼんやり知っていたのとクロージャを使うメリットは見易さ+細かい設定を簡易なオブジェクトとして”保持できる”ところに利便性があるっぽいという所まで掴めました。 もう少しクロージャを使いこなしたいと思っているので、遅延格納型プロパティについてはまだ輪郭しか掴めていませんが、ひとまず何が分からないかが分からない状態から抜け出せたのでとても勉強になるベストアンサーありがとうございました(^^)
TsukubaDepot

2020/10/02 08:30

いま改めて、「詳解Swift第5版」pp.339-342 の遅延格納型プロパティの章を見直していたのですが、たとえば「即時実行クロージャとして記述した方が、そのプロパティ専用の処理として記述できるため、用途が名確認になる(一部改変)」ともありました。 つまり、たった一度しか処理しない内容であれば、メソッド(関数)を定義するより、クロージャで記述し即時実行させたほうが、「一度しか呼ばない処理」という意図が明確になる、ということかと思います(これは、直接的には lazy キーワードとは関係ない話でもありますが)。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問