swiftLintを使い始めて、Swiftのベストプラクティスの1つが強制キャストを避けることだと気づいた。しかし、私はtableViewやcollectionViewでセルを処理するときにそれをよく使った:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
もしこれがベストプラクティスでないなら、これを処理する正しい方法は何でしょうか?としてif letを使うことはできると思いますが、else条件では空のセルを返す必要があるということでしょうか?それでいいのでしょうか?
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
// code
} else {
// code
}
この質問はおそらく意見に基づくものだと思うので、私の答えは大目に見てほしいが、ダウンキャストを強いることが常に悪いとは言わない。
として!SomeClass`はコントラクトであり、基本的にこれはSomeClassのインスタンスであることを保証します。 SomeClassでないことが判明した場合、例外がスローされます。
このコントラクトを使用しているコンテキストと、強制ダウンキャストを使用しなかった場合にどのような適切なアクションを取ることができるかを考慮する必要があります。
あなたが挙げた例では、もし dequeueReusableCellWithIdentifier
が MyOffersViewCell
を doesn't 与えるのであれば、おそらくあなたはセルの再利用識別子に関する何かを間違って設定しており、例外がその問題を見つけるのに役立つでしょう。
もし条件付きダウンキャストを使ったのであれば、nilを得ることになるので、何らかの方法でそれを処理しなければなりません。 例外を投げる?これは確かに回復不可能なエラーであり、開発中に見つけたいものです。 あなたのコードが突然異なるタイプのセルを返し始めることはないでしょう。 もしコードをフォース・ダウンキャストでクラッシュさせれば、問題が発生した行をまっすぐに指すでしょう。
さて、Webサービスから取得したJSONにアクセスしている場合を考えてみましょう。 ウェブサービスに、あなたのコントロールの及ばない変更があるかもしれない。 アプリは機能しないかもしれませんが、少なくとも、単にクラッシュするのではなく、アラートを表示することができます:
**BAD - JSONが配列でない場合にクラッシュします。
let someArray=myJSON as! NSArray
...
より良い - 無効な JSON をアラートで処理する。
guard let someArray=myJSON as? NSArray else {
// Display a UIAlertController telling the user to check for an updated app..
return
}
**更新
しばらくSwiftlintを使っていたが、今ではすっかりゼロ戦解除教に改宗している(下の@Kevin'さんのコメントと同じ)。
オプショナルを強制解除しなければならない状況で、代わりに if let...
、guard let... else
、switch... case let...
を使えないことはない。
というわけで、最近はこうしている:
for media in mediaArray {
if let song = media as? Song {
// use Song class's methods and properties on song...
} else if let movie = media as? Movie {
// use Movie class's methods and properties on movie...
}
}
...あるいは、バグの起こりやすいif/else
の連鎖よりも、網羅的なswitch
文のエレガントさと安全性を好むなら、こうする:
switch media {
case let song as Song:
// use Song class's methods and properties on song...
case let movie as Movie:
// use Movie class's methods and properties on movie...
default:
// Deal with any other type as you see fit...
}
あるいは、flatMap()
を使って mediaArray
をそれぞれ [Song]
型と [Movie]
型の2つの 型付き 配列にすることもできる。しかし、それは質問(force-unwrap)の範囲外です..._。
さらに、テーブルビューのセルをデキューイングするときにも、強制アンラップはしません。デキューされたセルが適切な UITableViewCell
サブクラスにキャストできない場合、それは私のストーリーボードに何か問題があることを意味します。
オリジナルの回答 (念のため)
Paulw11'さんの回答に加えて、このパターンは全く有効で、安全で、時には役に立つものです:
if myObject is String {
let myString = myObject as! String
}
Mediaインスタンスの配列で、
Songオブジェクトまたは
Movie`オブジェクト(どちらもMediaのサブクラス)を含むことができます:
let mediaArray = [Media]()
// (populate...)
for media in mediaArray {
if media is Song {
let song = media as! Song
// use Song class's methods and properties on song...
}
else if media is Movie {
let movie = media as! Movie
// use Movie class's methods and properties on movie...
}
quot;強制キャスト(Force Cast");は、例えばキャストするものがそのタイプであると分かっている場合に、その役割を果たす。
例えば、myView
が1
というタグを持つUILabel
のサブビューを持っていることが分かっている場合、UIView
からUILabel
へのフォースダウンキャストを安全に行うことができます:
myLabel = myView.viewWithTag(1) as! UILabel
あるいは、より安全なオプションはガードを使用することです。
guard let myLabel = myView.viewWithTag(1) as? UILabel else {
... //ABORT MISSION
}
後者の方が明らかに悪いケースを処理できるので安全ですが、前者の方が簡単です。つまり、将来変更されるかもしれない何かを考慮するか、アンラップしているものがキャストしたいものであるかどうか確信が持てない場合、そのような状況では常にガードが正しい選択となります。
要約すると、もしそれが何になるのかはっきりわかっているのであれば、強制的に投げることができます。そうでなければ、もしそれが他の何かになる可能性が少しでもあるのであれば、ガードを使いましょう。