Edited at

Swift3のIUO(!)型から属性への変更に考えさせられたこと

More than 1 year has passed since last update.

Swiftを初めてそろそろ半年くらいですが、未だに書き方のベストプラクティスを模索する毎日です。

今回もベストプラクティスがわからないケースがあったのでその一つについて書きたいと思います。

内容はOptionalと初期値についてです。


まず確認


Optionalとは

Optionalとは通常の型に加え、nilも入れられる型です。

使用するときは!をつけてアンラップする必要があります。

var a: String? //文字列のほか、nilを入れられます。

print(a!) // アンラップして用いる


Implicitly Unwrapped Optionalとは

Implicitly Unwrapped Optional(暗黙的なオプショナル型以後IUO)とは、

通常のオプショナルとは異なり、nilが入れられるにも関わらず非Optionalと同じように使えるOptionalのことです。

var a: String! = "1"

print(a)

こちらのIUOがSwift3.0では型から属性へ変更となったらしいです。

(属性に変更されたとは言いますが、特に何かが変わったようには思えない...。)


本題


IUOの使い道について

IUOはアンラップする必要がないため、とりあえずクラスのプロパティとして持っておいて、必要に応じて使うというようなことが多いように思います。

class ViewController: UIViewController, HogeHogeDelegate {

var hogeView: UIView!
@IBOutlet weak var scrollView: UIScrollView!

let hogehogeManager = HogeHogeManager()

override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
hogehogeManager.delegate = self

// 非同期処理が走る
hogehogeManager.hogeHoge()
}

override func viewDidDisappear(_ animated: Bool) {
hogehogeView.hidden = true
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

// MARK: HogeHogeDelegate
func didFinishHogeHoge(data: [String]) {

if /*dataを用いて判定*/ {
hogeView = UIView(frame: CGRect(x: 0, y: 0, width: scrollView.x, height: 100 * data.count))
}
}
}

このクラスにおいてhogeViewはIUOで宣言されています。

理由はHogeHogeManagerで取得した結果に応じて表示を切り替えたいからです。

このコードは非同期処理が終わらず、didFinishHogeHogeが呼ばれる前にviewDidDisappearがコールされるとforce unwrappingで落ちます。

Optionalは変数の中身がnilとなる可能性のあるときに使用するべきだと僕は思っています。

データとしてnilが入る可能性がないのであれば、Optionalで宣言するべきだからです。(この考えがそもそも間違っているのかもしれませんが...)

なので僕はこのコードであれば、最近は下のように書いています。

class ViewController: UIViewController, HogeHogeDelegate {

var hogeView: UIView = UIView()
@IBOutlet weak var scrollView: UIScrollView!

let hogehogeManager = HogeHogeManager()

:
:
}

あらかじめ初期値を設定しておけば、この後の処理で「今hogeViewって値入ってたっけ?nilだっけ?いや入っているはずだから使っちゃえ」というようなことは起きなくなるはずです。

これで気づかないnilで落ちることもなくなります。


IUOって属性としても残す必要はないのでは...?

先ほどまでの初期値設定を踏まえて思うのが、

そもそもIUOって必要ないのでは?ということです。

どういうことかと言いますと、

@IBOutlet weak var scrollView: UIScrollView!

このStoryBoard上のパーツを紐づける箇所についてですが、

これって別に普通のOptionalでも問題ないですよね?

@IBOutlet weak var scrollView: UIScrollView?

Viewがロードされない可能性がある(?)のでIUOになっているのはなんとなくわかるのですが、ここでIUOを使用したところでこの後ユーザの意図しないところで落ちるだけです。(objective-CとSwiftの混合プロジェクトだとiOS8でほぼ確実に落ちます)

それならあらかじめnilが入っているかもしれないと意識した上で作業できた方が良いのではないでしょうか。

実際の考えとしては以下のような感じです。

class ViewController: UIViewController, HogeHogeDelegate {

var hogeView: UIView = UIView()
@IBOutlet weak var scrollView: UIScrollView?

let hogehogeManager = HogeHogeManager()

override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
hogehogeManager.delegate = self

// 非同期処理が走る
hogehogeManager.hogeHoge()
}

override func viewDidDisappear(_ animated: Bool) {
hogehogeView.hidden = true
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

// MARK: HogeHogeDelegate
func didFinishHogeHoge(data: [String]) {
if /*dataを用いて判定(さっきの逆)*/ {
return
}

// optionalのときは必ずbindingとかchainして使うとか...。
if let scView = scrollView {
hogeView = UIView(frame: CGRect(x: 0, y: 0, width: scrollView.x, height: 100 * data.count))
}
}
}

scrollViewはnilかもしれないとわかっているので、bindingしたり、chainingして、

nilだった場合どうするかをきちんと考えることができます。

IUOだとその辺がとても曖昧で、nil判定すればいいのか、値が必ず入っているから大丈夫なのかわかりません。

なので、基本的に「初期値を設定する」か「Optionalで宣言する」で問題ないと思います。


まとめ


  • Swiftの場合どこで値を設定するのがベストなのか?


    • 個人的にはプロパティ宣言の時点で入れておいてあげればnil判定せずに済むと思う



  • IUOって必要なくない?


    • 初期値を設定するかOptionalで事足りる気がする



今のところ、Swiftを学び始めたものの、なかなかベストプラクティスが自分でもよくわからず、模索している最中です。

何か助言いただけたら嬉しいです。