前々回, 前回で外部のJSONファイルをデータとし, SwiftUIは表示の役割に集中するコードを作ることができました. しかし3-2-1で作成したJSONの読み込みコードは少し作りかたに不安があります.

今回は3-2-1のコードをより良いものにするのが目的です.具体的には以下のことです.

  • 例外処理をきちんと処理する
  • 例外が発生した時にエラーをコンソールに出す
  • Optional(!や?)をきちんと処理する

まずは読み込みの関数の改善です.

  • Resource.swiftの load( name: ext: ) の例外処理を行う
    • 処理を do { ~~ } で包む (doの中で発生した例外を拾うようになる)
    • try!は例外を潰してしまう ので, !を外した try に置き換える
    • tryで例外が起きた時の処理を catch { ~~ } に書く
      • catch内ではエラーをコンソールに出すprintを書く
      • データ取得に失敗したので, 空のData()をreturnする

コード1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import Foundation

class Resource {
    static func load( name:String, ext:String ) -> Data {
        do {
            let url = Bundle.main.url( forResource:name, withExtension:ext )!
            let data = try Data( contentsOf:url )
            return data
        }
        catch {
            print( "ファイルが読み込めませんでした." )
            return Data()
        }
    }
}

Appの見た目は変わりませんが, do ~ catchを導入したことでJSONファイルの中身が間違っていたときなど, Appは落ちなくなります. またコンソールにファイルが読み込めなかったと表示されるようになるため, JSONファイルがおかしいのかな?とわかりやすくなります.

このコードをさらに磨いてみましょう. Bundle,main.urlの ! に対応します.

  • Bundle.main.urlは最後に!をつけている(強制アンラップ)
    • ファイル名にミスがあると, Bundle.main.urlは nil (無) を返す
    • このように値の存在が保証されない変数を Optional変数 という(!や?のついた型)
      • URL
        • URL型. 中にURLが入っていることが保証される
      • URL?, URL!
        • Optional型. URLが入っていたり,いなかったりする(nil)
    • 中身がnilのOptional型を強制アンラップ(!をつける)しようとするとAppが落ちる
    • URLをミスしてもAppを落とさず,URLが間違っていることを伝えるようにしたい
  • guard let url = … else { ~(nilだった時の処理)~ } を書く

コード3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import Foundation

class Resource {
    static func load( name:String, ext:String ) -> Data {
        do {
            guard let url = Bundle.main.url( forResource:name, withExtension:ext )
            else {
                print( "ファイルがありません:\(name).\(ext)" )
                return Data()
            }
            let data = try Data( contentsOf:url )
            return data
        }
        catch {
            print( "ファイルが読み込めませんでした." )
            return Data()
        }
    }
}

guard let ~ else 構文はurlに値を入れようと試みます. 成功すればurlに値を入れて先に進みます.urlは値があるのが保証されますのでOptionalを外してくれます.

しかしBundle.main.urlが失敗した場合はnilになってしまいます.強制アンラップではAppが落ちますが,guardはurlへの代入の失敗を検知した時, elseブロックに処理を移動させます. guardのelseブロックの中ではurlが作れなかったことを伝える処理を書きます. 今回の場合URLの指定ミスになるので「ファイルがありません」とコンソールに表示するようにします.

以上2つの改善を加えることで, JSONファイルがない場合は「ファイルがありません」と出ますし, ファイルはあるがJSONの中身が間違っている場合などは「ファイルが読み込めませんでした」と別のエラーを指摘してくれるようになります.

続いてPhotoDataの改善も行います.

  • PhotoData.swiftを開き, static func parse( json: ) を例外に対応させる
    • 基本的に上記と同様に do { ~~~ } catch { ~~~ } の処理を書く
      • do{ ~ }で包む, try! は tryに置き換える
      • 例外発生時の処理をcatchに書く
      • error.locaizedDescription をprintする. 例外の内容がコンソール表示される
      • エラーの時は空の配列 をreturnする

コード2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation

struct PhotoData : Identifiable, Codable {
    var id:String = UUID().uuidString
    var type:String
    var imageName:String
    var title:String
    var message:String
    
    static func parse( json:Data ) -> [PhotoData] {
        do {
            let photos = try JSONDecoder().decode( [PhotoData].self, from:json )
            return photos
        }
        catch {
            print( "JSONをPhotoData配列に変換できませんでした." )
            print( error.localizedDescription )
            return []
        }
    }
}

PhotoData.parseの例外に対応すると何が良くなるかというと, Resource.loadではJSONファイルの読み込み自体はうまくいったけれど, JSONの形式が異なっていてPhotoDataに必要なデータがなくて変換できなかったことが伝えられるようになります.

このような例外やOptionalの処理を丁寧に書くのは手間はかかりますが, ミスがあった時にわかりやすいメッセージを出すことなどができるのでメリットは大きいです.